3.1.3线程同步
线程同步的访问方式也称为阻塞调用,即没有执行完任务不返回,线程被挂起。可以使用C#中的lock关键字,在此关键字范围类的代码都将是线程安全的。lock关键字需定义一个标记,线程进入锁定范围是必须获得这个标记。当锁定的是一个实例级对象的私有方法时使用方法本身所在对象的引用就可以了,将上面例子中的打印类Printer稍做改动,添加lock关键字,代码如下: //打印类 public class Printer { public void PrintNumbers() { //使用lock关键字,锁定d的代码是线程安全的 lock (this) { Console.WriteLine("-> {0} 正在执行打印任务,开始打印数字:", Thread.CurrentThread.Name); for (int i = 0; i < 10; i++) { Random r = new Random(); //为了增加冲突的几率及,使各线程各自等待随机的时长 Thread.Sleep(2000 * r.Next(5)); //打印数字 Console.Write("{0} ", i); } Console.WriteLine(); } } } } 同步后执行结果如下:
也可以使用System.Threading命名空间下的Monitor类进行同步,两者内涵是一样的,但Monitor类更灵活,这里就不在做过多的探讨,代码如下: //打印类 public class Printer { public void PrintNumbers() { Monitor.Enter(this); try { Console.WriteLine("-> {0} 正在执行打印任务,开始打印数字:", Thread.CurrentThread.Name); for (int i = 0; i < 10; i++) { Random r = new Random(); //为了增加冲突的几率及,使各线程各自等待随机的时长 Thread.Sleep(2000 * r.Next(5)); //打印数字 Console.Write("{0} ", i); } Console.WriteLine(); } finally { Monitor.Exit(this); } } } 输出结果与上面的一样。 3.2通过委托构建多线程应用程序 在看下面的内容时要求对委托有一定的了解,如果不清楚的话推荐参考一下博客园张子阳的《C# 中的委托和事件》,里面对委托与事件进行由浅入深的较系统的讲解: http://www.cnblogs.com/JimmyZhang/archive/2007/09/23/903360.html。 这里先举一个关于委托的简单例子,具体解说见注释: using System; namespace MultiThread { //定义一个指向包含两个int型参数、返回值为int型的函数的委托 public delegate int AddOp(int x, int y); class Program { static void Main(string[] args) { //创建一个指向Add()方法的AddOp对象p AddOp pAddOp = new AddOp(Add); //使用委托间接调用方法Add() Console.WriteLine("10 + 25 = {0}", pAddOp(10, 5)); Console.ReadLine(); } //求和的函数 static int Add(int x, int y) { int sum = x + y; return sum; } } } 运行结果为: 10 + 25 = 15 3.2.1线程异步 先说明一下,这里不打算讲解委托线程异步或同步的参数传递、获取返回值等,只是做个一般性的开头而已,如果后面有时间了再另外写一篇关于多线程中参数传递、获取返回值的文章。 注意观察上面的例子会发现,直接使用委托实例 pAddOp(10, 5) 就调用了求和方法 Add()。很明显,这个方法是由主线程执行的。然而,委托类型中还有另外两个方法——BeginInvoke()和EndInvoke(),下面通过具 体的例子来说明,将上面的例子做适当改动,如下: using System; using System.Threading; using System.Runtime.Remoting.Messaging; namespace MultiThread { //声明指向含两个int型参数、返回值为int型的函数的委托 public delegate int AddOp(int x, int y); class Program { static void Main(string[] args) { Console.WriteLine("******* 委托异步线程 两个线程“同时”工作 *********"); //显示主线程的唯一标示 Console.WriteLine("调用Main()的主线程的线程ID是:{0}.", Thread.CurrentThread.ManagedThreadId); //将委托实例指向Add()方法 AddOp pAddOp = new AddOp(Add); //开始委托次线程调用。委托BeginInvoke()方法返回的类型是IAsyncResult, //包含这委托指向方法结束返回的值,同时也是EndInvoke()方法参数 IAsyncResult iftAR = pAddOp.BeginInvoke(10, 10, null, null); Console.WriteLine(""nMain()方法中执行其他任务........"n"); int sum = pAddOp.EndInvoke(iftAR); Console.WriteLine("10 + 10 = {0}.", sum); Console.ReadLine(); } //求和方法 static int Add(int x, int y) { //指示调用该方法的线程ID,ManagedThreadId是线程的唯一标示 Console.WriteLine("调用求和方法 Add()的线程ID是: {0}.", Thread.CurrentThread.ManagedThreadId); //模拟一个过程,停留5秒 Thread.Sleep(5000); int sum = x + y; return sum; } } } 运行结果如下: ******* 委托异步线程 两个线程“同时”工作 ********* 调用Main()的主线程的线程ID是:10. Main()方法中执行其他任务........ 调用求和方法 Add()的线程ID是: 7. 10 + 10 = 20. 3.2.2线程同步 委托中的线程同步主要涉及到上面使用的pAddOp.BeginInvoke(10, 10, null, null)方法中后面两个为null的参数,具体的可以参考相关资料。这里代码如下,解释见代码注释: using System; using System.Threading; using System.Runtime.Remoting.Messaging; namespace MultiThread { //声明指向含两个int型参数、返回值为int型的函数的委托 public delegate int AddOp(int x, int y); class Program { static void Main(string[] args) { Console.WriteLine("******* 线程同步,“阻塞”调用,两个线程工作 *********"); Console.WriteLine("Main() invokee on thread {0}.", Thread.CurrentThread.ManagedThreadId); //将委托实例指向Add()方法 AddOp pAddOp = new AddOp(Add); IAsyncResult iftAR = pAddOp.BeginInvoke(10, 10, null, null); //判断委托线程是否执行完任务, //没有完成的话,主线程就做其他的事 while (!iftAR.IsCompleted) { Console.WriteLine("Main()方法工作中......."); Thread.Sleep(1000); } //获得返回值 int answer = pAddOp.EndInvoke(iftAR); Console.WriteLine("10 + 10 = {0}.", answer); Console.ReadLine(); } //求和方法 static int Add(int x, int y) { //指示调用该方法的线程ID,ManagedThreadId是线程的唯一标示 Console.WriteLine("调用求和方法 Add()的线程ID是: {0}.", Thread.CurrentThread.ManagedThreadId); //模拟一个过程,停留5秒 Thread.Sleep(5000); int sum = x + y; return sum; } } } 运行结果如下: ******* 线程同步,“阻塞”调用,两个线程工作 ********* Main() invokee on thread 10. Main()方法工作中....... 调用求和方法 Add()的线程ID是: 7. Main()方法工作中....... Main()方法工作中....... Main()方法工作中....... Main()方法工作中....... 10 + 10 = 20. 3.3BackgroundWorker组件 BackgroundWorker组件位于工具箱中,用于方便的创建线程异步的程序。新建一个WindowsForms应用程序,界面如下:
代码如下,解释参见注释: private void button1_Click(object sender, EventArgs e) { try { //获得输入的数字 int numOne = int.Parse(this.textBox1.Text); int numTwo = int.Parse(this.textBox2.Text); //实例化参数类 AddParams args = new AddParams(numOne, numTwo); //调用RunWorkerAsync()生成后台线程,同时传入参数 this.backgroundWorker1.RunWorkerAsync(args); } catch (Exception ex) { MessageBox.Show(ex.Message); } } //backgroundWorker新生成的线程开始工作 private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { //获取传入的AddParams对象 AddParams args = (AddParams)e.Argument; //停留5秒,模拟耗时任务 Thread.Sleep(5000); //返回值 e.Result = args.a + args.b; } //当backgroundWorker1的DoWork中的代码执行完后会触发该事件 //同时,其执行的结果会包含在RunWorkerCompletedEventArgs参数中 private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { //显示运算结果 MessageBox.Show("运行结果为:" + e.Result.ToString(), "结果"); } } //参数类,这个类仅仅起到一个记录并传递参数的作用 class AddParams { public int a, b; public AddParams(int numb1, int numb2) { a = numb1; b = numb2; } } 注意,在计算结果的同时,窗体可以随意移动,也可以重新在文本框中输入信息,这就说明主线程与backgroundWorker组件生成的线程是异步的。 4.总结 本文从线程、进程、应用程序的关系开始,介绍了一些关于多线程的基本概念,同时阐述了线程异步、线程同步及并发问题等。最后从应用角度出发,介 绍了如何通过System.Threading命名空间的类、委托和BackgroundWorker组件等三种手段构建多线程应用程序。 |
C#中构建多线程应用程序 2
最新推荐文章于 2024-03-08 09:15:23 发布
C#中构建多线程应用程序 2
2009-11-28 20:51