【问题标题】:Await or Task.FromResultAwait 或 Task.FromResult
【发布时间】:2015-01-09 23:19:04
【问题描述】:

可以说,我有一项服务,

public interface ISomeService
{
    Task<bool> DoSomeExpensiveCheckAsync(string parameter);
}

我有这个类来使用服务。它只需要做一些简单的空检查,然后返回服务响应。

public class SomeServiceConsumer
{
    private readonly ISomeService _serviceClient;

    public SomeServiceConsumer(ISomeService serviceClient)
    {
        _serviceClient = serviceClient;
    }

    public async Task<bool> DoSomething1Async(string someParameter)
    {
        if (string.IsNullOrWhiteSpace(someParameter))
        {
            return false;
        }
        return await _serviceClient.DoSomeExpensiveCheckAsync(someParameter);
    }

    //No async or await keywords   
    public Task<bool> DoSomething2Async(string someParameter)
    {
        if (string.IsNullOrWhiteSpace(someParameter))
        {
            return Task.FromResult(false);
        }
        return _serviceClient.DoSomeExpensiveCheckAsync(someParameter);
    }
}

我应该使用DoSomething1Async 还是DoSomething2Async

根据this answer,我不应该使用不必要的await 进行包装,但我必须使用Task.FromResult(false) 进行短路,如DoSomething2Async 中一样

但根据this answer,在某些情况下,try/catchusing 声明我实际上应该在返回之前await

那我说的对吗

  1. 如果我必须使用try/catchusing,那么我应该使用await

  2. 否则,如果您只打算返回,请不要await。并使用Task.FromResult进行短路

我更喜欢DoSomething1Async,如果有人说没关系,我想在任何地方都这样做:)。

【问题讨论】:

    标签: c# .net task-parallel-library async-await


    【解决方案1】:

    如果担心的话,缓存Task:

    static readonly Task<bool> falseTask = Task.FromResult(false);
    

    async 关键字还将异常包装在返回的Task 中,以及适当的堆栈跟踪。这是一个权衡,行为安全性。

    让我们看看各自不同的不同场景:

    async Task UseSomething1Async(string someParameter)
    {
        // if IsNullOrWhiteSpace throws an exception, it will be wrapped in
        // the task and not thrown here.
        Task t1 = DoSomething1Async(someParameter);
    
        // rather, it'll get thrown here. this is best practice,
        // it's what users of Task-returning methods expect.
        await t1;
    
        // if IsNullOrWhiteSpace throws an exception, it will
        // be thrown here. users will not expect this.
        Task t2 = DoSomething2Async(someParameter);
    
        // this would never have been reached.
        await t2;
    }
    

    这里只是为了说明这一点——IsNullOrWhiteSpace 实际上并没有出于任何原因抛出任何异常。

    就堆栈跟踪而言,异步堆栈跟踪由您await 的位置确定。否await 表示该方法将从堆栈跟踪中消失。

    DoSomeExpensiveCheckAsync 抛出异常。对于DoSomething1Async,堆栈跟踪将类似于caller -&gt; DoSomething1Async -&gt; DoSomeExpensiveCheckAsync

    对于DoSomething2Async,堆栈跟踪看起来像caller -&gt; DoSomeExpensiveCheckAsync。根据代码的复杂性,这可能会使调试变得困难。

    在实践中,如果我知道在它之前不会抛出异常,并且方法名称只是转发到另一个重载的重载,我通常只会直接返回 Task。这条规则总是有例外的,一定会有你想要最大化性能的地方。仔细挑选,意识到您可能会让您和您的用户的生活变得更加艰难。

    【讨论】:

    • 谢谢科里。我不明白它如何影响行为安全。请参阅我对@I3arnon 回答的评论。
    【解决方案2】:

    没关系。如果您对始终使用 async 关键字标记 Task-returning 方法感到满意,那么请继续使用 DoSomething1

    正如你所说,这是一个权衡:

    • DoSomething2 不会生成 async 方法所需的状态机,因此它的速度快(但差异几乎可以忽略不计)。

      李>
    • 另一方面,它可能会对异常处理产生一些无法预料的副作用,因为在 async 方法中,异常将存储在返回的 Task 中,而在另一种情况下,它会定期抛出。

    【讨论】:

    • 我不明白@I3arnon 的异常区别。如果我让服务中的 DoSomeExpensiveCheckAsync 抛出一个显式异常,那么 "var from1 = await someServiceCustomer.DoSomething1Async("Hello");"和“ var from2 = await someServiceCustomer.DoSomething2Async("Hello");"抛出完全相同的异常。 DoSomething1Async 在堆栈跟踪中确实有一个额外的 TaskAwaiter.ThrowForNonSuccess 。这确实表明 DoSomething1Async 正在做一些额外的事情,但我没有得到无法预料的副作用部分。
    • @labroo 要了解差异,您需要考虑没有await 会发生什么:var task = DoSomethingAsync("Hello")。如果有异常并且方法是异步的,则不会引发异常。异常将存储在任务中并在await 上重新抛出。如果方法不是异步的,就会像任何其他方法一样抛出异常。
    【解决方案3】:

    要回答你自己的问题,你需要问自己这个问题:方法的哪一部分是真正的async 部分?我想我们都同意真正的async 部分是调用代码_serviceClient.DoSomeExpensiveCheckAsync 的时间。因此,DoSomething2Async 更像是一个 hack。根据MSDN

    当您执行返回 Task 对象的异步操作并且该 Task 对象的结果已经计算出来时,此方法很有用。

    换句话说,如果您已经计算了真正异步部分的结果,或者您已将其缓存,您可以在其上使用Task.FromResult。但是,使用 Task.FromResult(false) 会撒谎并破解整个机制。

    在某些情况下,您可能需要使用 Task.FromResult 来执行并非真正异步的工作,但该工作可能需要一段时间,因为它是 CPU 密集型的,在这些情况下,可能会出现异常以避免冻结 UI。

    总之,DoSomething1Async 更合适。

    【讨论】:

      猜你喜欢
      • 2019-01-18
      • 1970-01-01
      • 1970-01-01
      • 2016-03-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多