简体   繁体   English

为什么要使用 async 并返回 await,什么时候可以返回 Task<t> 直接地?</t>

[英]Why use async and return await, when you can return Task<T> directly?

Is there any scenario where writing method like this:没有这样写方法的场景:

public async Task<SomeResult> DoSomethingAsync()
{
    // Some synchronous code might or might not be here... //
    return await DoAnotherThingAsync();
}

instead of this:而不是这个:

public Task<SomeResult> DoSomethingAsync()
{
    // Some synchronous code might or might not be here... //
    return DoAnotherThingAsync();
}

would make sense?有道理吗?

Why use return await construct when you can directly return Task<T> from the inner DoAnotherThingAsync() invocation?当您可以直接从内部DoAnotherThingAsync()调用返回Task<T>时,为什么要使用return await构造?

I see code with return await in so many places, I think I might have missed something.我在很多地方看到带有return await的代码,我想我可能漏掉了什么。 But as far as I understand, not using async/await keywords in this case and directly returning the Task would be functionally equivalent.但据我了解,在这种情况下不使用 async/await 关键字并直接返回 Task 在功能上是等效的。 Why add additional overhead of additional await layer?为什么要增加额外await层的额外开销?

There is one sneaky case when return in normal method and return await in async method behave differently: when combined with using (or, more generally, any return await in a try block).当在普通方法中return和在async方法中return await表现不同时,有一种偷偷摸摸的情况:当与 using 结合using时(或者更一般地说,在try块中的任何return await )。

Consider these two versions of a method:考虑这两个版本的方法:

Task<SomeResult> DoSomethingAsync()
{
    using (var foo = new Foo())
    {
        return foo.DoAnotherThingAsync();
    }
}

async Task<SomeResult> DoSomethingAsync()
{
    using (var foo = new Foo())
    {
        return await foo.DoAnotherThingAsync();
    }
}

The first method will Dispose() the Foo object as soon as the DoAnotherThingAsync() method returns, which is likely long before it actually completes.第一个方法将在DoAnotherThingAsync()方法返回后立即Dispose() Foo对象,这可能在它实际完成之前很久。 This means the first version is probably buggy (because Foo is disposed too soon), while the second version will work fine.这意味着第一个版本可能有问题(因为Foo处理得太快了),而第二个版本可以正常工作。

If you don't need async (ie, you can return the Task directly), then don't use async .如果您不需要async (即,您可以直接返回Task ),则不要使用async

There are some situations where return await is useful, like if you have two asynchronous operations to do:在某些情况下return await很有用,比如你有两个异步操作要做:

var intermediate = await FirstAsync();
return await SecondAwait(intermediate);

For more on async performance, see Stephen Toub's MSDN article and video on the topic.有关async性能的更多信息,请参阅 Stephen Toub 的MSDN 文章和有关该主题的视频

Update: I've written a blog post that goes into much more detail.更新:我写了一篇更详细的 博客文章

The only reason you'd want to do it is if there is some other await in the earlier code, or if you're in some way manipulating the result before returning it.您想要这样做的唯一原因是早期代码中是否存在其他await ,或者您在返回结果之前以某种方式操作结果。 Another way in which that might be happening is through a try/catch that changes how exceptions are handled.可能发生这种情况的另一种方式是通过改变异常处理方式的try/catch If you aren't doing any of that then you're right, there's no reason to add the overhead of making the method async .如果您没有这样做,那么您是对的,没有理由增加使方法async的开销。

Another case you may need to await the result is this one:您可能需要等待结果的另一种情况是:

async Task<IFoo> GetIFooAsync()
{
    return await GetFooAsync();
}

async Task<Foo> GetFooAsync()
{
    var foo = await CreateFooAsync();
    await foo.InitializeAsync();
    return foo;
}

In this case, GetIFooAsync() must await the result of GetFooAsync because the type of T is different between the two methods and Task<Foo> is not directly assignable to Task<IFoo> .在这种情况下, GetIFooAsync()必须等待GetFooAsync的结果,因为两个方法之间的T类型不同,并且Task<Foo>不能直接分配给Task<IFoo> But if you await the result, it just becomes Foo which is directly assignable to IFoo .但是,如果您等待结果,它就会变成Foo可以直接分配给IFoo Then the async method just repackages the result inside Task<IFoo> and away you go.然后异步方法只是将结果重新打包到Task<IFoo> ,然后你就走了。

If you won't use return await you could ruin your stack trace while debugging or when it's printed in the logs on exceptions.如果您不使用 return await 您可能会在调试时或在异常日志中打印时破坏您的堆栈跟踪。

When you return the task, the method fulfilled its purpose and it's out of the call stack.当您返回任务时,该方法实现了它的目的,并且它不在调用堆栈中。 When you use return await you're leaving it in the call stack.当您使用return await时,您会将其留在调用堆栈中。

For example:例如:

Call stack when using await: A awaiting the task from B => B awaiting the task from C使用 await 时调用堆栈:A 等待 B 的任务 => B 等待 C 的任务

Call stack when not using await: A awaiting the task from C, which B has returned.使用 await 时调用堆栈:A 等待来自 C 的任务,而 B 已返回。

Making the otherwise simple "thunk" method async creates an async state machine in memory whereas the non-async one doesn't.使原本简单的“thunk”方法异步在内存中创建一个异步状态机,而非异步则没有。 While that can often point folks at using the non-async version because it's more efficient (which is true) it also means that in the event of a hang, you have no evidence that that method is involved in the "return/continuation stack" which sometimes makes it more difficult to understand the hang.虽然这通常可以指出人们使用非异步版本,因为它更有效(这是真的),但这也意味着在挂起的情况下,您没有证据表明该方法涉及“返回/继续堆栈”这有时会让人更难理解这个问题。

So yes, when perf isn't critical (and it usually isn't) I'll throw async on all these thunk methods so that I have the async state machine to help me diagnose hangs later, and also to help ensure that if those thunk methods ever evolve over time, they'll be sure to return faulted tasks instead of throw.所以是的,当性能不重要时(通常不是),我将在所有这些 thunk 方法上抛出异步,以便我有异步状态机来帮助我稍后诊断挂起,并帮助确保如果那些thunk 方法会随着时间的推移而发展,它们肯定会返回错误的任务而不是 throw。

This also confuses me and I feel that the previous answers overlooked your actual question:这也让我感到困惑,我觉得以前的答案忽略了你的实际问题:

Why use return await construct when you can directly return Task from the inner DoAnotherThingAsync() invocation?当您可以从内部 DoAnotherThingAsync() 调用中直接返回 Task 时,为什么要使用 return await 构造?

Well sometimes you actually want a Task<SomeType> , but most time you actually want an instance of SomeType , that is, the result from the task.好吧,有时您实际上想要一个Task<SomeType> ,但大多数时候您实际上想要一个SomeType的实例,即任务的结果。

From your code:从您的代码:

async Task<SomeResult> DoSomethingAsync()
{
    using (var foo = new Foo())
    {
        return await foo.DoAnotherThingAsync();
    }
}

A person unfamiliar with the syntax (me, for example) might think that this method should return a Task<SomeResult> , but since it is marked with async , it means that its actual return type is SomeResult .不熟悉语法的人(例如我)可能会认为这个方法应该返回一个Task<SomeResult> ,但由于它被标记为async ,这意味着它的实际返回类型是SomeResult If you just use return foo.DoAnotherThingAsync() , you'd be returning a Task, which wouldn't compile.如果您只使用return foo.DoAnotherThingAsync() ,您将返回一个无法编译的任务。 The correct way is to return the result of the task, so the return await .正确的做法是返回任务的结果,所以return await

Another reason for why you may want to return await : The await syntax supports automatic conversion between Task<T> and ValueTask<T> return types.您可能想要return await的另一个原因: await语法支持Task<T>ValueTask<T>返回类型之间的自动转换。 For example, the code below works even though SubTask method returns Task<T> but its caller returns ValueTask<T> .例如,即使 SubTask 方法返回Task<T>但其调用者返回ValueTask<T> ,下面的代码仍然有效。

async Task<T> SubTask()
{
...
}

async ValueTask<T> DoSomething()
{
  await UnimportantTask();
  return await SubTask();
}

If you skip await on the DoSomething() line, you'll get a compiler error CS0029:如果您跳过DoSomething()行上的 await,您将收到编译器错误 CS0029:

Cannot implicitly convert type 'System.Threading.Tasks.Task<BlaBla>' to 'System.Threading.Tasks.ValueTask<BlaBla>'.无法将类型“System.Threading.Tasks.Task<BlaBla>”隐式转换为“System.Threading.Tasks.ValueTask<BlaBla>”。

You'll get CS0030 if you try to explicitly typecast it.如果您尝试显式类型转换,您将获得 CS0030。 Don't bother.不要打扰。

This is .NET Framework, by the way.顺便说一下,这是 .NET Framework。 I can totally foresee a comment saying "that's fixed in .NET hypothetical_version ", I haven't tested it.我完全可以预见到评论说“在 .NET hypothetical_version中已修复”,我还没有测试过。 :) :)

Another problem with non-await method is sometimes you cannot implicitly cast the return type, especially with Task<IEnumerable<T>> :非等待方法的另一个问题是有时您不能隐式转换返回类型,尤其是对于Task<IEnumerable<T>>

async Task<List<string>> GetListAsync(string foo) => new();

// This method works
async Task<IEnumerable<string>> GetMyList() => await GetListAsync("myFoo");

// This won't work
Task<IEnumerable<string>> GetMyListNoAsync() => GetListAsync("myFoo");

The error:错误:

Cannot implicitly convert type 'System.Threading.Tasks.Task<System.Collections.Generic.List>' to 'System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable>'无法将类型“System.Threading.Tasks.Task<System.Collections.Generic.List>”隐式转换为“System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable>”

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM