Await or Task.FromResult

本文关键字:FromResult Task or Await | 更新日期: 2023-09-27 18:30:07

我有一个服务让我们说,

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

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

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

根据这个答案,我不应该用不必要的await包裹,但我必须像DoSomething2Async一样使用Task.FromResult(false)短路

但根据这个回答,有些情况下try/catchusing陈述,我实际上应该在返回之前await

那么我说的对吗,那

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

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

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

Await or Task.FromResult

如果您担心它,请缓存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 -> DoSomething1Async -> DoSomeExpensiveCheckAsync

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

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

这并不重要。如果您习惯始终使用 async 关键字标记Task返回方法,请继续使用 DoSomething1

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

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

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

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

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

因此,换句话说,如果您已经计算了真正异步部分的结果或对其进行了缓存,则可以对其进行Task.FromResult。但是,使用Task.FromResult(false)将是撒谎并入侵整个机制。

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

总之,DoSomething1Async更合适。