开发者

C# .NET Tasks: How to get notification when multiple tasks have completed

开发者 https://www.devze.com 2023-03-25 02:04 出处:网络
In my WPF application, I need to run 2 long running tasks in parallel, both which return data that needs to be shown in the UI.

In my WPF application, I need to run 2 long running tasks in parallel, both which return data that needs to be shown in the UI.

I have a a property in my view model called IsBusy, which should be true until both tasks have completed.

How to I get a notification when the 2 long running tasks have completed?

I don't want to use Task.WaitAll, because it will block my UI t开发者_StackOverflow社区hread. I don't want to chain the tasks using ContinueWith, because I want those long running tasks to run in parallel.


Use TaskScheduler.FromCurrentSynchronizationContext() and pass it to TaskFactory.ContinueWhenAll(...) to do UI update after both tasks completed.


One way would be to create a third thread that spawns those tasks, and uses Task.WaitAll to delay thread exit until the two tasks complete. At the end of that thread you can fire an event or execute a callback function.

You could make it even easier by using BackgroundWorker, which has a built-in event called RunWorkerCompleted. This would put you on the thread pool so you don't have to be as concerned with managing that thread.


Try Task.Factory.ContinueWhenAll. It takes an array of Tasks and asynchronously fires off an operation when they are all complete.


What about having a boolean flag(s) corresponding to each task, and another separate watcher thread that monitors those flags? Have each task set their corresponding flags when they return from processing. Then, the monitoring thread watches to see if the flags are set (ie. both tasks are finished.) If the processes are finished, fire an event, set IsBusy to false, etc.


I think your answer is a callback.

When setting up your long-running threads, pass in a reference to a method. You may either pass the callback method into the task method, or you may set up an AsyncCallback delegate instance, which is built into the BeginInvoke() feature of delegates.

Here's a basic example:

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();
}  

Using the AsyncCallback feature, your callback method has to conform to a particular delegate definition that accepts the IAsyncResult, but you don't have to worry about invoking it properly in your method; the framework handles the callback for you:

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.
}

Both of these models should work just fine for you. Other options include setting up an event, which is then raised by each method when it completes. It would work in pretty much the same way, since events are really just "multi-cast" delegates with some syntax sugar.

0

精彩评论

暂无评论...
验证码 换一张
取 消