【问题标题】:Can new C# language features be used to to clean-up Task.WhenAll syntax?可以使用新的 C# 语言功能来清理 Task.WhenAll 语法吗?
【发布时间】:2018-05-07 15:03:58
【问题描述】:

借助“无处不在的异步”,触发多个异构操作的能力变得越来越频繁。当前的Task.WhenAll 方法将其结果作为数组返回,并要求所有任务返回相同类型的对象,这使得它的使用有点笨拙。我希望喜欢能够写...

var (i, s, ...) = await AsyncExtensions.WhenAll(
                          GetAnIntFromARemoteServiceAsync(),
                          GetAStringFromARemoteServiceAsync(),
                          ... arbitrary list of tasks   
                         );
Console.WriteLine($"Generated int {i} and string {s} ... and other things");

我能想到的最好的实现是

public static class AsyncExtensions
{
  public static async Task<(TA , TB )> WhenAll<TA, TB>(Task<TA> operation1, Task<TB> operation2)
  {
             return (await operation1, await operation2;
  }
}

这有一个缺点,我需要实现最多 N 个参数的单独方法。根据this answer,这只是使用泛型的限制。此实现也有一个限制,即不能支持返回 void 的任务,但这不是问题。

我的问题是:任何即将推出的语言功能是否允许更简洁的方法来解决这个问题?

【问题讨论】:

  • 你的 WhenAll 重载可以重写为 public static async Task&lt;(TA a, TB b)&gt; WhenAll&lt;TA, TB&gt;(Task&lt;TA&gt; operation1, Task&lt;TB&gt; operation2) { return (await operation1, await operation2); } 这避免了创建一段时间的新对象,一堆演员,并且更容易阅读。 (并且它会随着您增加对象而扩大)。
  • @BatteryBackupUnit 在第一个操作完成之前不会开始第二个操作。
  • @NeilMacMullen 当您到达新方法的主体时,任务已经在运行(您接受任务,而不是产生任务的函数),因此即使您当时试过。 WhenAll 根本没有引入并行性。它只是创建一个新任务,在完成所有提供的任务后完成。依次等待所有的人做同样的事情。

标签: c# asynchronous task c#-8.0


【解决方案1】:

dotnet/csharplang repository 上有一个open feature-request

该问题还提到了另一个开放功能请求,tuple splatting,这在某种程度上可能会有所帮助。如何,解释here

这两个问题目前都被标记为 [讨论] 和 [功能请求],并且已经“闲置”了一年(2017 年 5 月 - 2018 年 5 月)。

因此我推断答案(目前)是“否”。


采用扩展方式 Joseph Musser 确实写了一大堆这些内容供我们复制和粘贴:https://gist.github.com/jnm2/3660db29457d391a34151f764bfe6ef7

【讨论】:

    【解决方案2】:

    从 .NET 6 开始,标准库中没有可用的 API 允许 await 多个异构任务,并在值元组中获取它们的结果。

    我想指出,这是这个答案的要点,你在问题中显示的实现是不正确的。

    // Incorrect
    public static async Task<(T1, T2)> WhenAll<T1, T2>(Task<T1> task1, Task<T2> task2)
    {
        return (await task1, await task2);
    }
    

    这不是WhenAll。这是WhenAllIfSuccessful_Or_WhenFirstFails。如果task1 失败,错误将立即传播,task2 将成为一劳永逸的任务。在某些情况下,这可能正是您想要的。但通常你不想忘记你的任务,让它们在后台运行而不被观察。您希望等待所有这些都完成,然后再继续下一步的工作。这是实现WhenAll方法的更好方法:

    // Good enough
    public static async Task<(T1, T2)> WhenAll<T1, T2>(Task<T1> task1, Task<T2> task2)
    {
        await Task.WhenAll(task1, task2).ConfigureAwait(false);
        return (task1.Result, task2.Result);
    }
    

    这将等待两个任务完成,如果失败,它将传播第一个失败任务的错误(参数列表中的第一个,而不是按时间顺序)。在大多数情况下,这完全没问题。但是,如果您发现自己处于需要传播所有异常的情况,那就变得棘手了。下面是我所知道的最短的实现,它精确地模仿了原生 Task.WhenAll 的行为:

    // Best
    public static Task<(T1, T2)> WhenAll<T1, T2>(Task<T1> task1, Task<T2> task2)
    {
        return Task.WhenAll(task1, task2).ContinueWith(t =>
        {
            if (t.IsCanceled)
            {
                // Propagate the correct token
                CancellationToken ct = default;
                try { t.GetAwaiter().GetResult(); }
                catch (OperationCanceledException oce) { ct = oce.CancellationToken; }
                return Task.FromCanceled<(T1, T2)>(ct);
            }
            if (t.IsFaulted)
            {
                var tcs = new TaskCompletionSource<(T1, T2)>();
                tcs.SetException(t.Exception.InnerExceptions);
                return tcs.Task;
            }
            return Task.FromResult((task1.Result, task2.Result));
        }, default, TaskContinuationOptions.ExecuteSynchronously |
            TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap();
    }
    

    【讨论】:

      猜你喜欢
      • 2021-01-26
      • 1970-01-01
      • 2014-02-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-24
      • 1970-01-01
      • 2011-06-01
      相关资源
      最近更新 更多