.Net多线程总结(二)-BackgroundWorker

news/2024/11/5 13:28:51
导读:
  上篇文章介绍了多种线程的创建方式,以及winform在多线程编程时的特殊性,这篇我们来介绍一下异步编程的经典模式和微软对其的实现
  微软推荐的异步操作模型是事件模型,也即用子线程通过事件来通知调用者自己的工作状态,也就是设计模式中的observer模式,也可以看成是上文中线程类的扩展,最后实现后调用效果类似于
  
  MyThread thread=newMyThread()
  
  
  thread.Work+=newThreadWork(Calculate)
  
  
  thread.WorkComplete+=newWorkComplete(DisplayResult)
  
  
  
  
  Calculate(objectsender, EventArgs e)){
  
  
  ....
  
  
  }
  
  
  
  
  DisplayResult(objectsender, EventArgs e)){
  
  
  ...
  
  
  }
  
  
  
  
  
  
   <例一>
  这个话题已经有许多很好的文章,大家参考http://www.cnblogs.com/net66/archive/2005/08/03/206132.html,其作者在文章后附加有示例项目,项目中的线程类实现了事件发送,线程终止,报告任务进度等一系列必要的功能,大家可以自己去查看代码,我就不赘述了,我主要谈微软对这个模式的实现 BackgroundWorker
  上篇文章里说到了控制权的问题,上面的模型在winform下使用有个问题就是执行上下文的问题,在回调函数中(比如 <例一> 中的DisplayResult中),我们不得不使用BeginInvoke,才能调用ui线程创建的控件的属性和方法,
  比如在上面net66的例子里
  
  
  //创建线程对象
  _Task =newnewasynchui();
  
  //挂接进度条修改事件
  _Task.TaskProgressChanged +=newTaskEventHandler( OnTaskProgressChanged1 );
  
  
  //在UI线程,负责更新进度条
  privatevoidOnTaskProgressChanged1( objectsender,TaskEventArgs e )
  
  {
  
  if(InvokeRequired ) //不在UI线程上,异步调用
  {
  
  TaskEventHandler TPChanged1 =newTaskEventHandler( OnTaskProgressChanged1 );
  
  this.BeginInvoke(TPChanged1,newobject[] {sender,e});
  
  Console.WriteLine("InvokeRequired=true");
  
  }
  
  else
  {
  progressBar.Value =e.Progress;
  
  }
  
  }
  
  
  
  
   <例二>
  可以看到,在函数里面用到了
  if(InvokeRequired)
  {...BeginInvoke....}
  else
  {....}
  这个模式来保证方法在多线程和单线程下都可以运行,所以线程逻辑和界面逻辑混合在了一起,以至把以前很简单的只需要一句话的任务:progressBar.Value = e.Progress;搞的很复杂,如果线程类作为公共库来提供,对编写事件的人要求会相对较高,那么有什么更好的办法呢?
  其实在.Net2.0中微软自己实现这个模式,制作了Backgroundworker这个类,他可以解决上面这些问题,我们先来看看他的使用方法
  
  System.ComponentModel.BackgroundWorker bw =newSystem.ComponentModel.BackgroundWorker();
  
  
  //定义需要在子线程中干的事情
  bw.DoWork +=newSystem.ComponentModel.DoWorkEventHandler(bw_DoWork);
  
  
  //定义执行完毕后需要做的事情
  bw.RunWorkerCompleted +=newSystem.ComponentModel.RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
  
  
  //开始执行
  bw.RunWorkerAsync();
  
  
  
  
  staticvoidbw_RunWorkerCompleted(objectsender, System.ComponentModel.RunWorkerCompletedEventArgs e)
  
  {
  
  MessageBox.Show("Complete"+Thread.CurrentThread.ManagedThreadId.ToString());
  
  }
  
  
  
  
  staticvoidbw_DoWork(objectsender, System.ComponentModel.DoWorkEventArgs e)
  
  {
  
  MessageBox.Show(Thread.CurrentThread.ManagedThreadId);
  
  }
  
  
  
  
  
  
   <例三>
  注意我在两个函数中输出了当前线程的ID,当我们在WindowsForm程序中执行上述代码时,我们惊奇的发现,bw_RunWorkerCompleted这个回调函数居然是运行在UI线程中的,也就是说在这个方法中我们不用再使用Invoke和BeginInvoke调用winform中的控件了, 更让我奇怪的是,如果是在ConsoleApplication中同样运行这段代码,那么bw_RunWorkerCompleted输出的线程id和主线程id就并不相同.
  那么BackgroundWorker到底是怎么实现跨线程封送的呢?
  阅读一下这个类的代码,我们发现他借助了AsyncOperation.Post(SendOrPostCallback d, object arg)
  在winform下使用这个函数,就可以使得由SendOrPostCallback定义被封送会UI线程,聪明的博友可以用这个方法来实现自己的BackgroundWorker.
  继续查看下去,发现关键在于AsyncOperation的syncContext字段,这是一个SynchronizationContext类型的对象,而这个对象的Post方法具体实现了封送,当我继续查看
  SynchronizationContext.Post方法时,里面简单的令人难以执行
  
  publicvirtualvoidPost(SendOrPostCallback d, objectstate)
  
  {
  
  ThreadPool.QueueUserWorkItem(newWaitCallback(d.Invoke), state);
  
  }
  
  
  
  这是怎么回事情呢,线程池本省并不具备线程封送的能力啊联想到在Winform程序和Console程序下程序的行为是不同的,而且SynchronizationContext的Post方法是一个virtual方法,我猜测这个方法可能被继承自他的类重写了查询Msdn,果然发现在这个类有两个子类,其中一个就是 WindowsFormsSynchronizationContext,我们来看看这个类的Post方法
  
  publicoverridevoidPost(SendOrPostCallback d, objectstate)
  
  {
  
  if(this.controlToSendTo !=null)
  
  {
  
  this.controlToSendTo.BeginInvoke(d, newobject[] { state });
  
  }
  
  }
  
  
  哈哈,又是熟悉的beginInvoke,原来控制台程序和Winform程序加载的SynchronizationContext是不同的,所以行为才有所不同,通过简单的测试,我们可以看到控制台程序直接使用基类(SynchronizationContext),而winform程序使用这个WindowsFormsSynchronizationContext的Post方法把方法调用封送到控件的线程.
   总结:
  同事这个类还提供了进度改变事件,允许用户终止线程,功能全面,内部使用了线程池,能在一定成都上避免了大量线程的资源耗用问题,并通过SynchronizationContext解决了封送的问题,让我们的回调事件代码逻辑简单清晰,推荐大家使用.

本文转自
http://www.cnblogs.com/yizhu2000/archive/2007/10/19/929930.html



http://www.niftyadmin.cn/n/3657844.html

相关文章

调整ubunntu18桌面字体以及图标大小

安装gnome-tweaks桌面配置软件 sudo apt install gnome-tweaks 然后按下altf2&#xff0c;在弹出的窗口中输入下列指令 gnome-tweaks 就可以到font中进行字体大小的调节了&#xff0c;调节结束&#xff0c;重启就生效了。

List添加Item时, 如何去判断某个字段, 并且保证字段数据不重复性

来自与微软专家Liang Ming的讨论SharePoint中如何使得用户不能通过对WSS的List中添加或修改使得某个指定字段存在重复数据。并需要尽可能满足以下需求&#xff1a; 1&#xff09; 希望解决方案比较简单&#xff0c;并且易于部署&#xff08;客户希望在30分钟内能完成部署&…

调试SharePoint web part时, 如何可以多个人同时启动调试?

By Ben如果同时启动多个设计客户端--------------------------------------------------------------- 如果一个人在调试 web part , w3wp 进程会被这个人占用的. 其他人就没办法打开网站上任何的 asp.net 程序. 有什么好的办法, 可以让项目组成员可以同时启动调试而…

pytorch tensor求向量的模长

想要求pytorch tensor中某个2048维度的向量的模长&#xff0c;可以先相乘&#xff0c;然后再用sum求和。 假设 v是一个2048维的向量&#xff0c;则可以利用一下两个语句求出模长的平方。 sq v * vsum_sq sq.sum()

发布一个List记录查找Web Part: PowerSearch v1.0

By BenWeb Part PowerSearch for SharePoint-------------------------------------------------------------------------使用过wss的朋友都知道, 自带的full-text search 功能不能只在指定的一个list里查询符合条件的记录, 即使使用了dataview web part来查询查询条件也很有限…

pytorch 将tensor 类型转为python中的常用数据类型

假设变量y是pytorch中的一个Tensor类型&#xff0c;如下所示&#xff1a; y torch.sum(m) print(y) print(type(y)) print(y.item()) print(type(y.item())) 则使用y.item()则可以将其转化为float类型&#xff0c;程序输出结果如下所示&#xff1a; tensor(452.4124, devic…

缩小SQL SERVER日志文件

SQL server 2000 会有日志文件由于时间的积累越来越大的问题&#xff1a;数据库实际大小为15M, 日志文件实际大小为625KB(导出的日志文件), 但日志文件实际占用空间为200MB&#xff08;默认设置是文件日志会自动增长&#xff09;。如果想在数据库属性那里&#xff0c;直接将当前…

pytorch 中resnet如何快速加载官方提供的预训练模型

在做神经网络的搭建过程&#xff0c;经常使用pytorch中的resnet作为backbone&#xff0c;特别是resnet50,比如下面的这个网络设定 import torch import torch.nn as nn from torchvision import datasets, transforms from torchvision import modelsclass base_resnet(nn.Mod…