How to call one task after completion of multiple tasks

独自空忆成欢 提交于 2021-02-10 14:48:51

问题


I have List tasks, these tasks do some irrelevant work. What I need is to show message box in case all the tasks did their work correctly. I tried few ways, but nothing worked properly for me. Simplest way could be use

Task continuation = Tasks.WhenAll(tasks);
continuation.ContinueWith(obj => {
    show messagebox
});

but ContinueWith still runs for all tasks, not just for continuation task. I tried to set cancellation token or to use sign, but tasks run at same time, so it isn't working.

I will be grateful for any help.


回答1:


There is a difference between using Tasks in a non-async function and using it in an async function.

The short and easy answer to your question is not to use Task.WhenAll, but to use Task.WaitAll. This function returns as soon as all Tasks are finished. After that you can continue with the next statement, show the message box

private void OnButton1_clicked(object sender, ...)
{
    Task task1 = StartTask1(...);
    Task task2 = StartTask2(...);
    // do other things, after a while wait until all tasks are finished
    Task.WaitAll(new Task[] {task1, task2};
    MessageBox.Show(...)
 }

Problem: your UI is not responsive while waiting for the tasks to finished

An easy method to keep your UI responsive is using async - await. Async-await keeps your program responsive, because whenever if the procedure has to wait for something lengthy to finish, control is given back to your main program which has time to do other things.

The nice thing about async-await is that the code still looks sequential. You don't have to use ContinueWith in the meaning of "when this task is finished do this other task".

All you have to do to be able to use async-await is declare your event handler async. In the eventhandler you can call any other async function. Whenever you need the result of the called function you await for the task.

Your code would look like follows:

private async void OnButton1_clicked(object sender, ...)
{
    Task task1 = StartTask1(...);
    Task task2 = StartTask2(...);
    // while the tasks are being performed do other things,
    // after a while wait until all tasks are finished
    await Task.WhenAll(new Task[] {task1, task2};
    MessageBox.Show(...)
 }

This code makes sure that while waiting your UI is responsive. The only things I did to do this were:

  1. declare the event handler async
  2. Use WhenAll instead of WaitAll. WhenAll returns an (awaitable) Task
  3. Await for WhenAll to complete.

To be able to use async-await keep the following in mind:

  • If your function has to wait for something lengthy, start the lengthy as an async function and await for it
  • To be able to use await, your function must be declared async
  • Every async function returns Task instead of void and Task<Tresult> instead of TResult
  • There is one exception: an async event handler returns void
  • The return of await Task is void, the return of await Task<TResult> is Tresult

Eric Lippert (thanx Eric!) explained async-await as follows in Stackoverflow - async/await - Is this understanding correct?

Suppose for breakfast you have to toast bread and cook eggs. There are several scenarios for it:

  1. Start toasting bread. Wait until it is finished. Start cooking eggs, wait until it is finished. Synchronous processing. While you are waiting for the bread to toast you can't do anything else.
  2. Start toasting bread, while the bread is being toasted start cooking eggs. when the eggs are cooked wait until the bread finished toasting. This is called Asynchronous, but not concurrent. It is done by the main thread and as long as this thread does something, the main thread can't do anything else. But while it is waiting it has time to do other things (make some tea for instance)
  3. Hire cooks to toast the bread and cook the eggs. Wait until both are finished. Asynchronous and concurrent: the work is done by different threads. This is the most expensive because you have to start new threads.



回答2:


ContinueWith still runs for all tasks, not just for continuation task.

That's incorrect. ContinueWith will run once, when all tasks finish execution. You can simulate this quite easily:

var tasks = new[] { Task.Delay(1000), Task.Delay(2000), Task.Delay(500) };
Task.WhenAll(tasks).ContinueWith(x => Console.WriteLine("Finished"));
Console.Read();

You will only see "Finished" printed once.

If you can, I'd most certainly prefer to use await instead of attaching a continuation:

public async Task FooAsync()
{
    var tasks = new[] { Task.Delay(1000), Task.Delay(2000), Task.Delay(500) };
    await Task.WhenAll(tasks);
    Console.WriteLine("Done").
}


来源:https://stackoverflow.com/questions/33347177/how-to-call-one-task-after-completion-of-multiple-tasks

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!