线程同步中,还有一个比较流行的类<BackgroundWorker>.
BackgroundWorker 类允许您在单独的专用线程上运行操作。耗时的操作(如下载和数据库事务)在长时间运行时可能会导致用户界面 (UI) 似乎处于停止响应状态。如果您需要能进行响应的用户界面,而且面临与这类操作相关的长时间延迟,则可以使用 BackgroundWorker 类方便地解决问题。
若要在后台执行耗时的操作,请创建一个 BackgroundWorker,侦听那些报告操作进度并在操作完成时发出信号的事件。可以通过编程方式创建BackgroundWorker,也可以将它从“工具箱”的“组件”选项卡中拖到窗体上。如果在 Windows 窗体设计器中创建 BackgroundWorker,则它会出现在组件栏中,而且它的属性会显示在“属性”窗口中。
若要设置后台操作,请为 DoWork 事件添加一个事件处理程序。在此事件处理程序中调用耗时的操作。若要启动该操作,请调用 RunWorkerAsync。若要收到进度更新通知,请对 ProgressChanged 事件进行处理。若要在操作完成时收到通知,请对 RunWorkerCompleted 事件进行处理。
下面这个例子,主要从这几个方面来谈 cancellation support and report progress。
public partial class Form1 : Form { public Form1() { InitializeComponent(); this.backgroundWorker1.WorkerSupportsCancellation = true; this.backgroundWorker1.WorkerReportsProgress = true; this.backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork); this.backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted); this.backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged); } private void button1_Click(object sender, EventArgs e) { if (this.textBox1.Text != "" && this.textBox2.Text != "") { ab a = new ab(int.Parse(this.textBox1.Text), int.Parse(this.textBox2.Text)); this.backgroundWorker1.RunWorkerAsync(a); } } private void button2_Click(object sender, EventArgs e) { this.backgroundWorker1.CancelAsync(); } void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.progressBar1.Value = e.ProgressPercentage; } void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Cancelled) { this.textBox3.Text = "Result :Canclled!"; this.progressBar1.Value = 100; } else if (e.Error != null) { MessageBox.Show("Error Details : " + (e.Error as Exception).ToString()); } else { this.textBox3.Text = "Result :" + e.Result; this.progressBar1.Value = 100; } } void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { //do something here backgroundly. for (int i = 0; i < this.progressBar1.Maximum; i++) { Thread.Sleep(50); this.backgroundWorker1.ReportProgress(i); if (this.backgroundWorker1.CancellationPending) { e.Cancel = true; return; } } ab a = (ab)e.Argument; e.Result = a.C; } } class ab { private int _a; public int A { get { return _a; } set { _a = value; } } private int _b; public int B { get { return _b; } set { _b = value; } } public ab(int a,int b) { this._a = a; this._b = b; } public int C { get { return _b + _a; } } }
尽量的避免问题,我们最好不要在线程之间共享数据。如果要共享数据,就必须使用同步技术:确保一次只有一个线程访问和改变共享数据。其中不可避免的要用到“锁”。使用锁是需要时间的,并且,也不是总是必须的。所以对于一个类,我们可以创建一个类的2个版本,一个同步版本,一个异步版本。下面请看这个例子:
public class SynDemo { public virtual bool IsSynchronized { get { return false; } } public virtual void doThis() { //dothis } public virtual void doThat() { //doThat } public static SynDemo SynchronizeDemo(SynDemo d) { if (!d.IsSynchronized) { return new SynchronizedDemo(d); } return d; } private class SynchronizedDemo : SynDemo { private SynDemo _demo; private object _synRoot =new object(); public SynchronizedDemo(SynDemo d) { this._demo = d; } public override bool IsSynchronized { get { return true; } } public override void doThat() { lock(_synRoot) { _demo.doThat(); } } public override void doThis() { lock(_synRoot) { _demo.doThis(); } } } }
在c#中如何给线程传递参数呢?其实很简单。一般来说可以采用2种方式:
下面是一个Demo :
public static void Main() { //1.使用一个带参数的委托ParameterizedThreadStart来初始化Thread myData data = new myData(); data.Message = "new Info"; new Thread(paralizeThread).Start(data); //2.通过初始化一个类的字段,将该类的方法封装到线程中去 new Thread(new myThreadClass("new info2").ThreadMethod).Start(); } //类或结构都行 struct myData { string message; public string Message { get { return message; } set { message = value; } } } static void paralizeThread(object o) { myData data = (myData)o; Console.WriteLine("Thread {1} received a Parameter : {0}",data.Message,Thread.CurrentThread.ManagedThreadId); } class myThreadClass { string _message; public myThreadClass(string message) { this._message = message; } public void ThreadMethod() { Console.WriteLine("Thread {1} received a Parameter : {0}", this._message, Thread.CurrentThread.ManagedThreadId); } }
异步委托,首先要搞明白,为什么要用到异步委托。
长时间以来,我个人在编写代码的时候,都认为程序应该是执行完一件事之后,然后再干接下来的。这用计算机术语来讲,叫同步执行(Synchronous Execution)。但事实上,这样的执行效果不一定人性化。举下面的一组对照:
搞懂异步委托,需要理解BeginInvoke() ,EndInvoke() ,IAsyncResult 以及AsyncCallback 委托:
异步委托提供以异步方式调用同步方法的能力。当同步调用一个委托时,“Invoke”方法直接对当前线程调用目标方法。如果编译器支持异步委托,则它将生成“Invoke”方法以及“BeginInvoke”和“EndInvoke”方法。如果调用“BeginInvoke”方法,则公共语言运行库 (CLR) 将对请求进行排队并立即返回到调用方。将对来自线程池的线程调用该目标方法。提交请求的原始线程自由地继续与目标方法并行执行,该目标方法是对线程池线程运行的。如果在对“BeginInvoke”方法的调用中指定了回调方法,则当目标方法返回时将调用该回调方法。在回调方法中,“EndInvoke” 方法获取返回值和所有输入/输出参数。如果在调用“BeginInvoke”时未指定任何回调方法,则可以从调用“BeginInvoke”的线程中调用“EndInvoke”。
下面,我用一个例子,谈谈可能发生的4种情况
class Programe { private delegate void ADelegateSomewhat(int x); public static void Main() { ADelegateSomewhat aD = MathPupil; ////1.直接调用EndInvoke,这和直接调用MathPupil(100)的效果一样, ////但会等到这个方法执行完之后才去执行后面的语句 //IAsyncResult ar= aD.BeginInvoke(100,null,null); //aD.EndInvoke(ar); ////2.轮询,每隔50毫秒看看孩子算完了没. //IAsyncResult ar = aD.BeginInvoke(200, null, null); //while (!ar.IsCompleted) //{ // Console.Write("."); // Thread.Sleep(500); //} //aD.EndInvoke(ar); ////3.实际上和2的方法类似,只是把Sleep的时间放上来了 ////但事实上,功能会多点,比如控制跳出的条件等 //IAsyncResult ar = aD.BeginInvoke(300,null,null); //while (!ar.AsyncWaitHandle.WaitOne(500)) //{ // Console.Write("."); //} //aD.EndInvoke(ar); //4.回调函数,最有意义,设置相对麻烦点 IAsyncResult ar = aD.BeginInvoke(400, MathCallBack, aD); //现在干点别的事,不用关心算算术题的孩子了。 //由delegate的回调方法自动来管理 Console.ReadLine(); //如果不等待委托完成其任务就结束主线程,委托线程就会停止 } //假装自己一个小学生处理一道加法算术题,历时5秒钟 static void MathPupil(int x) { Console.WriteLine("Now I'll pretend to be a pupil"); int sum = 0; for (int i = 0; i < x; i++) { sum += i; Thread.Sleep(5000/x); } Console.WriteLine("After 5 secends, i've got the answer :" +sum); } static void MathCallBack(IAsyncResult ar) { ADelegateSomewhat aD = (ADelegateSomewhat)ar.AsyncState; aD.EndInvoke(ar); } }
我想,到目前为止,应该收回之前提到委托“从来不是一个重点”的言论了。
参考资料:<MSDN-magazine>:Asynchronous Method Execution Using Delegates