.net 既然可以直接返回Task,为什么还要使用async和return await< T>呢?

gdx19jrr  于 2023-01-31  发布在  .NET
关注(0)|答案(9)|浏览(273)

是否存在任何方案,其中编写方法如下所示:

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

而不是这样:

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

会有意义吗

    • 当您可以直接从内部DoAnotherThingAsync()调用返回Task<T>时,为什么要使用return await构造?**

我在很多地方看到了return await的代码,我想我可能错过了一些东西。但据我所知,在这种情况下不使用async/await关键字,直接返回任务在功能上是等效的。为什么要增加额外的await层的额外开销呢?

wa7juj8i

wa7juj8i1#

有一种狡猾的情况,普通方法中的returnasync方法中的return await表现不同:当与using(或更一般地,try嵌段中的任何return await)组合时。
考虑以下两种方法:

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

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

第一个方法在DoAnotherThingAsync()方法返回后立即Dispose()Foo对象,这可能在它实际完成之前很久,这意味着第一个版本可能有bug(因为Foo处理得太早),而第二个版本将工作正常。

sq1bmfud

sq1bmfud2#

如果不需要async(即可以直接返回Task),则不要使用async
在某些情况下,return await非常有用,例如,如果您有 * 两个 * 异步操作要执行:

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

有关async性能的更多信息,请参见Stephen Toub的MSDN articlevideo主题。

**更新:**我已经编写了一个blog post,其中包含了更多细节。

anauzrmj

anauzrmj3#

你想要这么做的唯一原因是如果在前面的代码中有其他的await,或者你在返回结果之前以某种方式操纵它。另一种可能发生的方式是通过try/catch来改变异常的处理方式。如果你没有这样做,那么你是对的。所以没有理由增加生成async方法的开销。

ffdz8vbo

ffdz8vbo4#

另一个你可能需要等待结果的例子是:

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

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

在这种情况下,GetIFooAsync()必须等待GetFooAsync的结果,因为T的类型在两个方法之间是不同的,并且Task<Foo>不能直接赋值给Task<IFoo>。它就变成了Foo,它 * 是 * 可以直接赋值给IFoo的,然后async方法就把结果重新打包到Task<IFoo>中,然后你就可以走了。

juud5qan

juud5qan5#

如果你不使用return await,你可能会在调试时或者在异常日志中打印堆栈跟踪时破坏堆栈跟踪。
当你返回任务时,这个方法完成了它的任务,它离开了调用堆栈。当你使用return await时,你把它留在了调用堆栈中。
例如:
使用await时调用堆栈:A等待B的任务=〉B等待C的任务

    • 不**使用await时调用堆栈:A正在等待来自C的任务,B已返回该任务。
62lalag4

62lalag46#

将简单的“thunk”方法异步化会在内存中创建一个异步状态机,而非异步状态机则不会。然而,这通常会让人们使用非异步版本,因为它更高效(这是真的)这也意味着在挂起的情况下,你没有证据表明那个方法涉及到“返回/继续堆栈”,这有时会使理解挂起变得更加困难。
所以,是的,当perf不是关键的时候(通常也不是),我会在所有这些thunk方法上抛出async,这样我就有了async状态机来帮助我在以后诊断挂起,也帮助确保如果这些thunk方法随着时间的推移而发展,它们一定会返回错误的任务,而不是抛出。

xu3bshqb

xu3bshqb7#

这也使我感到困惑,我觉得前面的回答忽略了你的实际问题:
既然可以直接从内部DoAnotherThingAsync()调用返回Task,为什么还要使用return await构造呢?
当然,有时候您实际上需要一个Task<SomeType>,但大多数情况下,您实际上需要SomeType的一个示例,即任务的结果。
从您的代码:

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

不熟悉语法的人(比如我)可能会认为这个方法应该返回一个Task<SomeResult>,但是因为它被标记为async,这意味着它实际的返回类型是SomeResult。如果你只使用return foo.DoAnotherThingAsync(),你将返回一个任务,这将不会编译。正确的方法是返回任务的结果,所以return await

hvvq6cgz

hvvq6cgz8#

另一个可能需要return await的原因是:使用await语法可以避免Task<T>ValueTask<T>类型不匹配。例如,即使SubTask方法返回Task<T>,但其调用者返回ValueTask<T>,下面的代码也能正常工作。

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

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

如果跳过DoSomething()行上的wait,将得到编译器错误CS0029:
无法将类型“System.Threading.Tasks.Task”隐式转换为“System.Threading.Tasks.ValueTask”。
如果尝试显式地对CS0030进行类型转换,也会得到CS0030。
顺便说一句,这是.NET框架。我完全可以预见会有评论说“这在.NET * hypothetic_version* 中得到修复",我还没有测试过它。:)

vzgqcmou

vzgqcmou9#

non-await方法的另一个问题是有时候你不能隐式地转换返回类型,特别是对于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");

错误:
无法将类型"System. Threading. Tasks. Task"隐式转换为"System. Threading. Tasks. Task" <System.Collections.Generic.List>' to 'System.Threading.Tasks.Task <System.Collections.Generic.IEnumerable>'

相关问题