Await或Task.FromResult

时间:2014-11-12 22:56:03

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

我有一个服务可以说,

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进行短路

  3. 我更喜欢DoSomething1Async,如果有人说这无关紧要,我想在任何地方都这样做。)。

3 个答案:

答案 0 :(得分:16)

如果您对此感到担心,请缓存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表示该方法将从堆栈跟踪中消失。

Say DoSomeExpensiveCheckAsync抛出异常。在DoSomething1Async的情况下,堆栈跟踪看起来像caller -> DoSomething1Async -> DoSomeExpensiveCheckAsync

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

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

答案 1 :(得分:4)

它并不重要。如果您对始终标记Task感到满意 - 使用async关键字返回方法,请继续使用DoSomething1

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

  • DoSomething2无法生成async方法所需的状态机,因此它的稍微更快(但不同之处在于

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

答案 2 :(得分:-1)

要回答您自己的问题,您需要问自己一个问题:方法的哪个部分是真正的async部分?我想我们都可以同意真正的{{ 1}}部分是何时调用代码async。因此,_serviceClient.DoSomeExpensiveCheckAsync比任何东西都更容易被黑客入侵。根据{{​​3}}:

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

因此,换句话说,如果您已经计算了真正异步部分的结果或对其进行了缓存,则可以在其上使用DoSomething2Async。但是,使用Task.FromResult会说谎并破坏整个机制。

在某些情况下,您可能需要使用Task.FromResult(false)来完成并非真正异步的工作,但是由于占用大量CPU资源,因此可能需要花费一些时间,在某些情况下,您可能会例外以免冻结UI。

总而言之,Task.FromResult更合适。