【问题标题】:What's the difference between awaiting async Task function and calling await inside a void function?等待异步任务函数和在 void 函数中调用 await 有什么区别?
【发布时间】:2016-07-14 18:51:51
【问题描述】:

要理解这个问题,请查看以下示例的await 调用和函数InitSyncContext() 的定义。

基于此,我想知道程序在每种情况下的行为方式,因为我不完全理解调用 await InitSyncContext(store) 和在内部调用 await 而不返回 Task 之间的区别.

作为参考,我之前做过研究,我发现了一个类似的example here,但我认为它在我的情况下有所不同。

*以下代码是真实世界代码的简化示例,仅用于演示目的。

void Main()
{
    Initializer();
}

private async void Initializer()
{
    var store = InitLocalStore();
    await InitSyncContext(store); // <-- Here, await call
    InitFileSync(store);
}

// Here returns Task (without having a return inside. No compile errors)
private async Task InitSyncContext(MobileServiceSQLiteStore store)
{
    await Client.SyncContext.InitializeAsync(store);
}

//------------- Second Method -------------

void Main()
{
    Initializer();
}

private void Initializer()
{
    var store = InitLocalStore();
    InitSyncContext(store); // <-- Here without await call
    InitFileSync(store);
}

// Here is void but with a async call inside
private async void InitSyncContext(MobileServiceSQLiteStore store)
{
    await Client.SyncContext.InitializeAsync(store);
}

【问题讨论】:

  • 您不能在非异步方法中使用 await 关键字。它不是 C# 语言中的有效语法。这不会编译
  • @SteveCooper 感谢您指出这一点。我纠正了这个例子。此代码取自真实世界的代码(但没有 Main 定义),因此我可以对其进行测试。这样就可以编译了。
  • @MickyD - 坚持这个作为答案。我会投票...
  • @hbob 实际上是的,这就是问题所在,但我的问题是await 在类似环境中的行为(不一定使用 Xamarin,而是 C#)。要在类构造方法中使用await,我实现了此处描述的工厂模式blog.stephencleary.com/2013/01/async-oop-2-constructors.html,并且我没有尝试在控制台应用程序中使用Xamarin。

标签: c# .net asynchronous async-await task


【解决方案1】:

等待异步任务函数和在 void 函数中调用 await 有什么区别?

这也许是一个非常大的问题,最好阅读 Stephen Cleary 先生的精彩文章 Async/Await - Best Practices in Asynchronous Programming

一些总结点:

避免 async void - 优先使用 async Task 方法而不是 async void 方法

另见

【讨论】:

  • 建议添加在参考网站上找到的答案摘要
【解决方案2】:

等待async Task 函数和在void 函数中调用await 有什么区别?

全世界!一切……然后是一些。让我们退后一步,从一些基础知识开始。

你应该“一直异步”,即;如果您要返回 awaitableTaskTask&lt;T&gt;),则应正确使用 asyncawait 关键字。

这里有几件事情需要澄清。您不应该有 async void 方法,而是应该有 async Task - 异常是事件处理程序(其中委托被预定义为非任务返回)等。让我们检查并挑选方法一(我将忽略 Main 入口点):

一个

private async void Initializer()
{
    var store = InitLocalStore();
    await InitSyncContext(store); // <-- Here, await call
    ...
}

// Here returns Task (without having a return inside. No compile errors)
private async Task InitSyncContext(MobileServiceSQLiteStore store)
{
    await Client.SyncContext.InitializeAsync(store);
}

您有一个 Initializer 方法,它立即是代码气味,因为它是 async void。这应该是async Task,并且应该有“Async”后缀。此外,您有一个InitSyncContext,它采用store 变量并调用一些客户端初始化工作。这里的代码味道是您使用的是asyncawait。像这样的简单(单任务)工作负载不需要这样做。相反,您应该简单地使用 return 关键字。最底部的示例。我们来看方法二:

两个

private void Initializer()
{
    var store = InitLocalStore();
    InitSyncContext(store); // <-- Here without await call
    ...
}

// Here is void but with a async call inside
private async void InitSyncContext(MobileServiceSQLiteStore store)
{
    await Client.SyncContext.InitializeAsync(store);
}

事情正式变得越来越糟!由于对异步命名法的误解,我们假设由于一种方法似乎“正常”工作,而没有考虑另一种方法可能效仿的最佳实践。你创建了InitSyncContext 方法async void。方法不应该是async void 的原因是它们是即发即弃的。内部异步状态机没有Task 可锁定,因此状态丢失。当您删除调用者的 await 时,您说的是启动此异步操作,但您并不关心它的结果。

这是实现所需功能的正确方法:

正确

private async Task InitializerAsync()
{
    var store = InitLocalStore();
    await InitSyncContextAsync(store);
    ...
}

// Simply return the task that represents the async operation and let the caller await it
private Task InitSyncContextAsync(MobileServiceSQLiteStore store)
{
    return Client.SyncContext.InitializeAsync(store);
}

关于来自 sn-ps 的 Main 方法的注释,如果这是控制台应用程序的一部分 - 异步堆栈的顶级入口点将调用 .Result.Wait()

进一步阅读

【讨论】:

  • 当您说“异步堆栈的顶级入口点将调用.Result 或.Wait()”时,您的意思是需要在Main() 中做一些特别的事情吗?那会是什么?如果我们“一路异步”,你如何处理Main不是异步的?
  • @hbob,是的。由于Main 正在调用async 方法链,因此我们需要确保在其上调用.Wait()。当然假设这是一个控制台应用程序。
  • 哦,您的意思是在控制台的 Main 函数中(如在 OP 的示例中),我们将执行 InitializerAsync().Wait() 并使其“一直异步”? (虽然这会阻塞,但我想这是有道理的,所以应用程序不会退出。)
  • @hbob 对,在控制台应用程序中,在应用程序的入口点没有对 async 的原生支持。想一想,如果您在命令行上并尝试调用.exe,它不知道Task 是什么。请参阅herehere 了解更多详情。
【解决方案3】:

在第一个示例中,编译器生成如下内容:

TheadPool.RunTask(()=>InitSyncContext(store)).ContinueWith(()=>InitFileSync(store))

第二次——没什么有趣的。因为没有人等待任务结束。所有调用都将在主线程中,例如 await Client.SyncContext.InitializeAsync(store);

【讨论】:

  • 我知道。这是简化。
猜你喜欢
  • 2021-11-18
  • 1970-01-01
  • 1970-01-01
  • 2019-08-28
  • 2021-09-27
  • 1970-01-01
  • 2020-12-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多