在等待之前检查异步任务是否同步完成的正确模式

Correct pattern to check if an async Task completed synchronously before awaiting it

我有一堆请求要处理,其中一些可能会同步完成。 我想收集所有立即可用的结果,并尽早 return 它们,同时等待其余结果。

大致是这样的:

List<Task<Result>> tasks = new ();
List<Result> results = new ();

foreach (var request in myRequests) {
  var task = request.ProcessAsync();
  if (task.IsCompleted)
    results.Add(task.Result);  // or  Add(await task)  ?
  else 
    tasks.Add(task);
}

// send results that are available "immediately" while waiting for the rest
if (results.Count > 0)  SendResults(results);

results = await Task.WhenAll(tasks);
SendResults(results);

我不确定依赖 IsCompleted 是否是个坏主意;会不会出现结果不可信,或者可能再次变回 false 等情况?

同样,即使在检查了 IsCompleted 之后,使用 task.Result 会不会很危险,应该总是更喜欢 await task 吗?如果我们使用 ValueTask 而不是 Task 会怎样?

I'm not sure whether relying on IsCompleted might be a bad idea; could there be situations where its result cannot be trusted...

如果您处于多线程上下文中,则 IsCompleted 可能 return 在您检查它时为 false,但它会立即完成。在像您使用的代码这样的情况下,发生这种情况的成本会非常低,所以我不会担心。

or where it may change back to false again, etc.?

不可以,一旦任务完成,就无法取消。

could it be dangerous to use task.Result even after checking IsCompleted.

不,那应该总是安全的。

should one always prefer await task?

await 是一个很好的默认值,当你没有特定的理由去做其他事情时,但是有很多其他模式可能有用的用例。您突出显示的用例是一个很好的例子,您希望 return 完成任务的结果而不等待所有任务。

正如 Stephen Cleary 在下面的评论中提到的,使用 await 来维护预期的异常行为可能仍然是值得的。您可能会考虑做更像这样的事情:

var requestsByIsCompleted = myRequests.ToLookup(r => r.IsCompleted);

// send results that are available "immediately" while waiting for the rest
SendResults(await Task.WhenAll(requestsByIsCompleted[true]));
SendResults(await Task.WhenAll(requestsByIsCompleted[false]));

What if were using ValueTask instead of Task?

以上答案同样适用于这两种类型。

您可以使用这样的代码在等待其他任务完成的同时不断发送已完成任务的结果。

foreach (var request in myRequests)
{
    tasks.Add(request.ProcessAsync());
}

// wait for at least one task to be complete, then send all available results
while (tasks.Count > 0)
{
    // wait for at least one task to complete
    Task.WaitAny(tasks.ToArray());

    // send results for each completed task
    var completedTasks = tasks.Where(t => t.IsCompleted);
    var results = completedTasks.Where(t => t.IsCompletedSuccessfully).Select(t => t.Result).ToList();
    SendResults(results);

    // TODO: handle completed but failed tasks here

    // remove completed tasks from the tasks list and keep waiting
    tasks.RemoveAll(t => completedTasks.Contains(t));
}

仅使用 await 即可实现所需的行为:


async Task ProcessAsync(MyRequest request, Sender sender)
{
     var result = await request.ProcessAsync();
     await sender.SendAsync(result);   
}

...

async Task ProcessAll()
{

   var tasks = new List<Task>();
   foreach(var request in requests) 
   {
      var task = ProcessAsync(request, sender);
      // Dont await until all requests are queued up
      tasks.Add(task);
   }
   // Await on all outstanding requests 
   await Task.WhenAll(tasks);

}



已经有很好的答案,但除此之外,这里也是我的建议,关于如何处理多个任务并以不同的方式处理每个任务,也许它会满足您的需求。我的示例是事件,但您可以将它们替换为适合您需要的某种状态管理。

    public interface IRequestHandler
    {
        event Func<object, Task> Ready;
        Task ProcessAsync();
    }

    public class RequestHandler : IRequestHandler
    {
        // Hier where you wraps your request:
        // private object request;
        private readonly int value;

        public RequestHandler(int value)
            => this.value = value;

        public event Func<object, Task> Ready;

        public async Task ProcessAsync()
        {
            await Task.Delay(1000 * this.value);
            // Hier where you calls:
            // var result = await request.ProcessAsync();
            //... then do something over the result or wrap the call in try catch for example
            var result = $"RequestHandler {this.value} - [{DateTime.Now.ToLongTimeString()}]";

            if (this.Ready is not null)
            {
                // If result passes send the result to all subscribers

                await this.Ready.Invoke($"RequestHandler {this.value} - [{DateTime.Now.ToLongTimeString()}]");
            }
        }
    }

    static void Main()
    {
        var a = new RequestHandler(1);
        a.Ready += PrintAsync; 
        var b = new RequestHandler(2);
        b.Ready += PrintAsync;
        var c = new RequestHandler(3);
        c.Ready += PrintAsync;
        var d= new RequestHandler(4);
        d.Ready += PrintAsync;
        var e = new RequestHandler(5);
        e.Ready += PrintAsync;
        var f = new RequestHandler(6);
        f.Ready += PrintAsync;

        var requests = new List<IRequestHandler>()
        {
            a, b, c, d, e, f
        };

        var tasks = requests
            .Select(x => Task.Run(x.ProcessAsync));

        // Hier you must await all of the tasks
        Task
            .Run(async () => await Task.WhenAll(tasks))
            .Wait();
    }

    static Task PrintAsync(object output)
    {
        Console.WriteLine(output);

        return Task.CompletedTask;
    }