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/catch
和using
陈述,我实际上应该在返回之前await
。
那么我说的对吗,那
如果我必须使用
try/catch
或using
那么我应该await
否则,如果您只打算返回,请不要
await
。并使用Task.FromResult
短路
我更喜欢DoSomething1Async
,如果有人说:)没关系,我想在任何地方都这样做。
如果您担心它,请缓存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
更合适。