.net 在Windows.Forms中执行后台任务的最简单方法是什么?
后台任务是指涉及网络I/O、磁盘I/O或其他可能在网络上发生或不发生的长时间运行任务的任务。它通常会与更新GUI的代码混合在一起,而GUI需要在另一个线程(GUI线程)上运行 简单的意思是,打开Form.cs文件时,源代码与以前一样容易阅读。实际上,源代码流仍然必须按照代码的执行顺序顺序读取,而不管它在哪个线程上执行。所有支撑结构必须可重复使用并隐藏在某处,而不是包含在每个表单中 谷歌搜索MSDN:发现微软官方认可的解决方案是System.ComponentModel.BackgroundWorker,它(非常!)缺少第二点.net 在Windows.Forms中执行后台任务的最简单方法是什么?,.net,winforms,multithreading,design-patterns,.net,Winforms,Multithreading,Design Patterns,后台任务是指涉及网络I/O、磁盘I/O或其他可能在网络上发生或不发生的长时间运行任务的任务。它通常会与更新GUI的代码混合在一起,而GUI需要在另一个线程(GUI线程)上运行 简单的意思是,打开Form.cs文件时,源代码与以前一样容易阅读。实际上,源代码流仍然必须按照代码的执行顺序顺序读取,而不管它在哪个线程上执行。所有支撑结构必须可重复使用并隐藏在某处,而不是包含在每个表单中 谷歌搜索MSDN:发现微软官方认可的解决方案是System.ComponentModel.BackgroundWor
(System.Windows.Threading.Dispatcher中还有官方认可的Silverlight/XAML/3.5解决方案模型。)您仍然可以使用
BackgroundWorker
。它不需要作为表单上的组件存在。您可以同样轻松地将其包装到一个类中,然后在每个表单中重用该类
然而,这与在需要时简单地为后台任务设置工作人员几乎没有什么不同。用于此您能解释一下为什么您说
后台工作人员
不足吗
在大多数情况下,它需要2-3行额外的代码。如果您真的不喜欢BackgroundWorker,您可以像我一样为后台操作创建自己的基类。这是我迄今为止提出的最简单的想法。这可能是完全不符合犹太教的,我编写的Windows.Forms应用程序非常接近于零 它涉及一个助手,它有两个主要方法,Background()和Foreground()。其中任何一个都接受以lambda表达式形式指定的委托。Background()在后台线程上启动任何给定的委托,并立即返回。前台()使用Form.BeginInvoke()将任何给定的委托“返回”到GUI线程,并立即返回 下面是一个代码示例,说明如何使用此设计模式,前提是已经实现了帮助器
public class Form1 : Form {
protected ProgressBar progressBar1;
protected Button button1;
protected BackgroundHelper helper = new BackgroundHelper();
public void button1_Click(...) {
// Execute code in the background.
helper.Background(() => {
for (int i = 0; i <= 100; i++) {
// Continually report progress to user.
helper.Foreground<int>(i, j => {
progressBar1.Value = j;
});
// Simulate doing I/O or whatever.
Thread.Sleep(25);
}
});
}
}
公共类表单1:表单{
受保护的ProgressBar progressBar1;
保护按钮1;
受保护的BackgroundHelper=新的BackgroundHelper();
公共无效按钮1\u单击(…){
//在后台执行代码。
helper.Background(()=>{
对于(int i=0;i{
progressBar1.值=j;
});
//模拟执行I/O或其他操作。
睡眠(25);
}
});
}
}
这样可以保持代码整齐有序,在适当的位置提供共享变量,并允许跨两个线程的循环
为了澄清助手的工作
- 构造函数启动一个等待队列的后台线程
- 后台()和前台()都会立即返回
- Background()使用内部队列将要在后台线程中运行的代码排队
- 前台()也会这样做,首先创建帮助程序的GUI线程上会出现BeginInvoke
例如,根据您提出的磁盘I/O问题,我最近编写了一个异步文件扫描程序类(或多年来第100次)。只要实例化一个新实例并连接到事件中,它就可以像您所希望的那样重用 我将功能封装在一个实现跨线程安全的类中。该类触发事件以通知调用方更新以及更新完成的时间 您说“源代码仍然必须按照代码的执行顺序顺序读取”,但要意识到线程之间是并行运行的。这就是为什么分离代码结构很好的原因 这演示了如何将可重用代码隔离到一个单独的类中
Public Class FileScanner
Public Event Scan_Complete(sender As Object)
Public Event Scan_Update(sender As Object, filename As String)
Public Event Scan_Error(sender As Object, ex As Exception)
Private Delegate Sub del_ScanComplete()
Sub New(syncObject As Control, path String)
Me.SynchronizeObject = syncObject
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf ScanFilesAsync), path)
End Sub
Private Sub ScanFilesAsync(ByVal state As Object)
' scan files here
' call the method to raise the Complete event
ScanComplete()
End Sub
Private Sub ScanComplete()
If SynchronizeObject.InvokeRequired Then
' we cant raise event on a different thread than our caller
' so lets invoke our method on the same caller thread
Dim d As New del_ScanComplete(AddressOf ScanComplete)
SynchronizeObject.Invoke(d)
Else
' no synchronize needed, tell the caller we are done
RaiseEvent Complete(Me)
End If
End Sub
End Class
在Windows窗体中执行后台任务并在完成后引发事件的另一种方法是使用和类。这里需要注意的是,必须在UI线程中创建AsyncOperation才能正常工作。否则,UI代码必须检查invokererequired
public class BackgroundTask{
private AsyncOperation _asyncOperation;
public EventHandler Done;
public BackgroundTask(){
_asyncOperation = AsyncOperationManager.CreateOperation();
}
public void DoAsync(object userState) {
System.Threading.ThreadPool.QueueUserWorkItem( ExecuteDo, userState);
}
private void ExecuteDo(object state) {
// Do your work here
// Raise event after finish
_asyncOperation.PostOperationCompleted( Finished, EventArgs.Empty );
}
private void LookUpFinished( object eventargs ) {
OnDone( ( EventArgs) eventargs );
}
private void OnDone( LookUpEventArgs e ) {
EventHandler localEvent = Done;
if ( localEvent!= null ) {
localEvent(this,e);
}
}
}
下面是如何使用它的代码:
public class MyForm : Form {
public MyForm() {
InitializeComponent();
}
protected override OnShown(EventArgs e) {
BackgroundTask task = new BackgroundTask();
task.Done += SignalTaskDone;
}
private void SignalTaskDone(object sender, EventArgs e){
MessageBox.Show(this, "Task finished");
}
}
正如非常有说服力的争论所指出的,Microsoft已经看到了BackgroundWorker中的缺陷,使用Task可以获得更好、更清晰的代码。使用async关键字运行。传统上,编写线程化代码一开始就是一项复杂的任务。就我个人而言,与早期.Net时代相比,后台工作者的方式非常简单,在早期.Net时代,您必须自定义滚动跨线程操作委托和回调。你能解释一下为什么它没有达到第二点吗?肯定比早期的dotnet更简单,同意。我在一个团队中工作,我们需要有一个标准的设计模式来完成这项工作,我们需要有非常可读的代码来实现协作。这是否足够详细,或者?你因为可读性、可读性和使用它所需的工作量而对Bgw打折。请记住,工作量是500倍,因为这是一个团队,有几个大型软件项目,这需要一次又一次地完成,每一个都需要一次……是的(希望如此):这不仅仅是需要多少额外的代码行的问题,但是BackgroundWorker的另一个问题是,它使原始的单线程代码无法读取。BackgroundWorker强制您将逻辑和一致的代码划分为一组事件处理程序和不同的方法。BG worker继承自组件库,因为它知道如何与其所有者控件同步,以进行跨线程安全调用。这就是为什么它“强制”您使用事件处理程序方法。如果您不需要线程安全,您可以始终使用threading.threadpool实现自己的线程类