简体   繁体   中英

Is it safe not to wait for all tasks

Say I have the following action method:

[HttpPost]
public async Task<IActionResult> PostCall()
{
    var tasks = new List<Task<bool>>();
    for (int i = 0; i < 10; i++)
        tasks.Add(Manager.SomeMethodAsync(i));

    // Is this line necessary to ensure that all tasks will finish successfully?
    Task.WaitAll(tasks.ToArray());

    if (tasks.Exists(x => x.Result))
        return new ObjectResult("At least one task returned true");
    else
        return new ObjectResult("No tasks returned true");
}

Is Task.WaitAll(tasks.ToArray()) necessary to ensure that all tasks will finish successfully? Will the tasks whose Result happened not to get accessed by the Exists finish their execution in the background successfully? Or is there a chance that some of the tasks (that weren't waited for) get dropped since they would not be attached to the request? Is there a better implementation I'm missing?

Yes, the tasks are not guaranteed to complete unless something waits for them (with something like an await)

In your case, the main change you should make is making the Task.WaitAll

await Task.WhenAll(tasks);

So it is actually asynchronous. If you just want to wait for a task to return, use WhenAny instead.

Under your provided implementation, the Task.WaitAll call blocks the calling thread until all tasks have completed. It would only proceed to the next line and perform the Exists check after this has happened. If you remove the Task.WaitAll , then the Exists check would cause the calling thread to block on each task in order; ie it first blocks on tasks[0] ; if this returns false, then it would block on tasks[1] , then tasks[2] , and so on. This is not desirable since it doesn't allow for your method to finish early if the tasks complete out of order.

If you only need to wait until whichever task returns true first, then you could use Task.WhenAny . This will make your asynchronous method resume as soon as any task completes. You can then check whether it evaluated to true and return success immediately; otherwise, you keep repeating the process for the remaining collection of tasks until there are none left.

If your code was running as an application (WPF, WinForms, Console), then the remaining tasks would continue running on the thread pool until completion, unless the application is shut down. Thread-pool threads are background threads, so they won't keep the process alive if all foreground threads have terminated (eg because all windows were closed).

Since you're running a web app, you incur the risk of having your app pool recycled before the tasks have completed. The unawaited tasks are fire-and-forget and therefore untracked by the runtime. To prevent this from happening, you can register them with the runtime through the HostingEnvironment.QueueBackgroundWorkItem method, as suggested in the comments.

[HttpPost]
public async Task<IActionResult> PostCall()
{
    var tasks = Enumerable
        .Range(0, 10)
        .Select(Manager.SomeMethodAsync)
        .ToList();

    foreach (var task in tasks)
        HostingEnvironment.QueueBackgroundWorkItem(_ => task);

    while (tasks.Any())
    {
        var readyTask = await Task.WhenAny(tasks);
        tasks.Remove(readyTask);
        if (await readyTask)
            return new ObjectResult("At least one task returned true");
    }

    return new ObjectResult("No tasks returned true");
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM