【发布时间】:2011-08-04 14:49:11
【问题描述】:
在我的 WPF 应用程序中,我需要并行运行 2 个长时间运行的任务,它们都返回需要在 UI 中显示的数据。
我的视图模型中有一个名为 IsBusy 的属性,在这两个任务完成之前它应该为真。
如何在 2 个长时间运行的任务完成后收到通知?
我不想使用 Task.WaitAll,因为它会阻塞我的 UI 线程。我不想使用 ContinueWith 链接任务,因为我希望那些长时间运行的任务并行运行。
【问题讨论】:
在我的 WPF 应用程序中,我需要并行运行 2 个长时间运行的任务,它们都返回需要在 UI 中显示的数据。
我的视图模型中有一个名为 IsBusy 的属性,在这两个任务完成之前它应该为真。
如何在 2 个长时间运行的任务完成后收到通知?
我不想使用 Task.WaitAll,因为它会阻塞我的 UI 线程。我不想使用 ContinueWith 链接任务,因为我希望那些长时间运行的任务并行运行。
【问题讨论】:
使用 TaskScheduler.FromCurrentSynchronizationContext() 并将其传递给 TaskFactory.ContinueWhenAll(...) 在两个任务完成后进行 UI 更新。
【讨论】:
一种方法是创建第三个线程来生成这些任务,并使用Task.WaitAll 延迟线程退出,直到两个任务完成。在该线程结束时,您可以触发事件或执行回调函数。
您可以使用BackgroundWorker 来简化它,它有一个名为RunWorkerCompleted 的内置事件。这将使您进入线程池,因此您不必担心管理该线程。
【讨论】:
试试Task.Factory.ContinueWhenAll。它需要一个 Tasks 数组,并在它们全部完成时异步触发一个操作。
【讨论】:
如果有一个对应于每个任务的布尔标志,以及另一个监控这些标志的单独的观察者线程呢?让每个任务在从处理中返回时设置其相应的标志。然后,监控线程观察是否设置了标志(即两个任务都已完成)。如果进程已完成,则触发一个事件,将 IsBusy 设置为 false,等等。
【讨论】:
我认为你的回答是回调。
设置长时间运行的线程时,请传入对方法的引用。您可以将回调方法传递给任务方法,也可以设置一个 AsyncCallback 委托实例,该实例内置于委托的 BeginInvoke() 功能中。
这是一个基本的例子:
public void StartMyTwoTasks()
{
//I'm passing the callback to the task method directly; you may prefer to pass it to BeginInvoke()
var task1Lambda = ()=>Task1(TaskCompleted);
var task2Lambda = ()=>Task2(TaskCompleted);
task1Lambda.BeginInvoke(null,null);
task2Lambda.BeginInvoke(null,null);
}
public void Task1(Action<int> task1Complete)
{
//perform your long-running calculation or data retrieval/transformation here
Thread.Sleep(10000);
//when finished, call the callback method.
//You may need to use Control.Invoke() to make sure the callback is executed on the UI thread
//If you use the AsyncCallback feature of BeginInvoke/EndInvoke, you don't have to make the call here.
taskComplete(1);
}
public void Task2(Action<int> taskComplete)
{
//Ditto
Thread.Sleep(8000);
taskComplete(2);
}
public void Task1Complete(int taskNumber)
{
TasksComplete[taskNumber-1] = true;
If(TasksComplete.All(x=>x==true))
DoSomethingOnceAllTasksAreComplete();
}
使用 AsyncCallback 功能,您的回调方法必须符合接受 IAsyncResult 的特定委托定义,但您不必担心在您的方法中正确调用它;框架为您处理回调:
public public void StartALongTask()
{
var taskLambda = ()=>PerformTask();
taskLambda.BeginInvoke(TaskComplete,null);
}
//one advantage is that you can call a method that returns a value very easily.
public IEnumerable<string> PerformTask()
{
//long-running task
Thread.Sleep(5000);
return Enumerable.Repeat("result", 100);
//look ma, no callback invocation.
//This makes asynchronous delegates very useful for refactoring a synchronous
//operation without a lot of code changes.
}
//Here's the callback, which is invoked properly by the runtime when the thread is complete
public void TaskComplete(IASyncResult ar)
{
//calling EndInvoke on the same delegate, which is available in the AsyncResult,
//returns the return value of that delegate as if you'd called it synchronously.
var results = ar.AsyncDelegate.EndInvoke(ar);
//Now you can do something with those results.
}
这两种模型都应该适合您。其他选项包括设置一个事件,然后在完成时由每个方法引发。它的工作方式几乎相同,因为事件实际上只是带有一些语法糖的“多播”委托。
【讨论】: