我有一个服务可以说,
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/catch
和using
个陈述的情况,我实际上应该await
才能返回。
我说的是正确的,
如果我必须使用try/catch
或using
,那么我应该await
如果您只是要返回,请不要await
。并使用Task.FromResult
进行短路
我更喜欢DoSomething1Async
,如果有人说这无关紧要,我想在任何地方都这样做。)。
答案 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
更合适。