【问题标题】:How to return a Task<T> without await如何在不等待的情况下返回 Task<T>
【发布时间】:2020-01-16 12:22:50
【问题描述】:

是否可以从一个方法返回一个任务,该方法首先调用多个Task&lt;T&gt; 返回方法,然后返回包含先前调用结果的某种类型,而不使用await

例如,下面是直截了当的:

public Task<SomeType> GetAsync() => FirstOrDefaultAsync();

但是,我想做这样的事情:

public Task<SomeType> GetAsync()
{
    var list = GetListAsync();   // <-- Task<List<T>>
    var count = GetCountAsync(); // <-- Task<int>

    return new SomeType // <-- Obviously compiler error
    {
        List  /* <-- List<T> */ = list,  // <-- Also compiler error
        Count /* <-- int     */ = count, // <-- Also compiler error
    };
}

有没有可能不用写就可以做到:

public async Task<SomeType> GetAsync()
{
    return new Type2
    {
        List = await GetListAsync(),
        Count = await GetCountAsync(),
    };
}

【问题讨论】:

  • 调用GetListAsync启动一个任务,返回值会在未来的某个时间产生,当你说await GetListAsync()时,你是在告诉等到收到返回值,所以你期望如何将将来某个时间现在生成的值赋给变量?
  • 为什么? async/await 的发明是为了使其更具可读性和可管理性。
  • @HenkHolterman 因为我读到有多个 await 对性能不利。我尝试等待最后一次通话
  • @TomVast 我以为你试图避免重构现有的代码库,但那是 wrong reason 这样做。
  • @TomVast - 你听错了。但也许您想将这两个任务与 await WhenAll() 重叠,具体取决于您的应用类型。

标签: c# async-await


【解决方案1】:

您可以将Task.WhenAllTask.ContinueWith 一起使用。

public Task<SomeType> GetAsync()
{
    var list = GetListAsync();
    var count = GetCountAsync();

    return Task.WhenAll(list, count).ContinueWith(_ => new Type2
    {
        List = list.Result,
        Count = count.Result,
    });
}

编辑

按照 cmets 中的建议,您最好只使用 await。我还建议阅读GSerg - Performance of Task.ContinueWith in non-async method vs. using async/await 链接的帖子

【讨论】:

  • 请注意,如果出现错误,它将抛出AggregateExceptions。你需要unwrap它。
  • 这行得通,但它更好吗?以什么方式?明智的做法是使用await
  • @HenkHolterman 这可能取决于实施。我假设他想在GetAsync 上使用await 并让GetListAsyncGetCountAsync 并行运行。但这取决于这两种方法的作用.. 只做await Task.WhenAll(...) 可能会更容易
  • @HenkHolterman 这需要将GetAsync 声明为async,这反过来又会使其所有调用者异步等。显然,OP 正在尝试使用异步而不将现有方法重构为异步。
  • @GSerg:不,async 只影响方法本身,而不影响调用者。无论哪种方式,它只返回一个Task
【解决方案2】:

坦率地说,问题中的版本是正确的

public async Task<SomeType> GetAsync()
{
    return new Type2
    {
        List = await GetListAsync(),
        Count = await GetCountAsync(),
    };
}

我知道你问过“不使用 await”,但是:避免 await 的技巧次优;特别是,您应该几乎从不使用ContinueWith - 这是旧版API,Task 实现现在针对await 而不是ContinueWith 进行了优化。

至于:

因为我读到有多个等待不利于性能。我尝试等待最后一次通话

没有;一旦你有一个不完整的等待,你有多少几乎都没有关系 - 它们实际上是免费的。一对零不完整await 的问题与ContinueWith 相当,因此:避免await 不会获得任何好处。

结论:只需使用await。它更简单、更直接,并且内部针对它进行了优化。

作为次要优化,您可能需要添加ConfigureAwait(false),即

public async Task<SomeType> GetAsync()
{
    return new Type2
    {
        List = await GetListAsync().ConfigureAwait(false),
        Count = await GetCountAsync().ConfigureAwait(false),
    };
}

或者如果它们应该同时运行,并且实现支持它:

public Task<SomeType> GetAsync()
{
    var list = GetListAsync(); 
    var count = GetCountAsync();

    return new SomeType
    {
        List = await list.ConfigureAwait(false),
        Count = await count.ConfigureAwait(false),
    };
}

【讨论】:

    【解决方案3】:

    问题在于Task.WhenAll 方法不接受具有不同结果类型的任务。所有任务必须属于同一类型。幸运的是,这很容易解决。下面的WhenAll 变体等待两个不同类型的任务,并返回一个具有组合结果的任务。

    public static Task<TResult> WhenAll<T1, T2, TResult>(
        Task<T1> task1, Task<T2> task2, Func<T1, T2, TResult> factory)
    {
        return Task.WhenAll(task1, task2).ContinueWith(t =>
        {
            var tcs = new TaskCompletionSource<TResult>();
            if (t.IsFaulted)
            {
                tcs.SetException(t.Exception.InnerExceptions);
            }
            else if (t.IsCanceled)
            {
                tcs.SetCanceled();
            }
            else
            {
                tcs.SetResult(factory(task1.Result, task2.Result));
            }
            return tcs.Task;
        }, default, TaskContinuationOptions.ExecuteSynchronously,
            TaskScheduler.Default).Unwrap();
    }
    

    可以这样使用:

    public Task<SomeType> GetAsync()
    {
        return WhenAll(GetListAsync(), GetCountAsync(),
            (list, count) => new SomeType { List = list, Count = count });
    }
    

    相对于其他解决方案的优势在于处理异常。如果GetListAsyncGetCountAsync 都失败,则从GetAsync 返回的任务将在浅层AggregateException 中保留这两个异常(不嵌套在另一个AggregateException 中)。

    顺便说一句,这个答案的灵感来自 Stephen Cleary 的回答 here

    【讨论】:

      猜你喜欢
      • 2018-10-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-31
      • 1970-01-01
      • 2014-12-10
      • 2016-09-12
      • 1970-01-01
      相关资源
      最近更新 更多