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

enz*_*nzi 1 c# async-await

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

大致是这样的:

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);
Run Code Online (Sandbox Code Playgroud)

我不确定依赖是否IsCompleted是一个坏主意;是否存在其结果不可信或可能再次变回false等情况?

task.Result同样,即使在检查之后使用是否会存在危险IsCompleted,是否应该始终选择await task?如果使用ValueTask代替呢Task

Str*_*ior 5

我不确定依赖 IsCompleted 是否是一个坏主意;是否存在其结果不可信的情况...

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

或者它可能会再次变回哪里false等等?

不,一旦任务完成,它就不能取消完成。

task.Result检查后使用会不会有危险IsCompleted

不,那应该总是安全的。

人们应该总是更喜欢吗await task

await当您没有特定原因做其他事情时,这是一个很好的默认值,但在许多用例中其他模式可能有用。您突出显示的用例是一个很好的示例,您希望返回已完成任务的结果而不等待所有任务。

正如 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]));

Run Code Online (Sandbox Code Playgroud)

如果使用ValueTask代替呢Task

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