【问题标题】:What exactly happens call async method without await keyword?在没有 await 关键字的情况下调用异步方法究竟会发生什么?
【发布时间】:2018-06-04 22:21:00
【问题描述】:

我有一个 Web 服务器,并且有定期的作业合并和发送记录(大量请求日志)。

Task.Run(() =>
{
    while (true)
    {
        try
        {
            MergeAndPutRecords();
        }
        catch (Exception ex)
        {
            Logger.Error(ex);
        }
    }
});

MergeAndPutRecords函数中有代码合并记录,async函数返回Task发送记录。 (实际上是 Amazon Kinesis Firehose 的 PutRecordBatchAsync。)

如果我在没有 await 关键字的情况下调用该函数会发生什么?函数是否在单独的线程上运行? Here says it is not。那么什么是返回任务手段? Here 表示没有 await 关键字的异步方法意味着

  1. 在当前线程上启动异步方法。忽略所有结果(包括例外)。

那么我的定期作业和 PutRecordBatchAsync 是同时处理的吗?我知道异步和并发是不同的。但是没有 await 关键字,它们在同一个线程中。哪个会先被执行?我很困惑……

会有大量记录需要实时合并和发送。所以我认为它必须同时执行..

【问题讨论】:

  • async 关键字本身不会做任何事情。每个await 组合成一个AsyncStateMachine,可以异步处理内容。如果async 方法中没有await 关键字,则没有理由创建AsyncStateMachine,因此您将获得同步方法。
  • await 用于等待结果到达。它不以任何方式控制获得结果的过程是如何开始或正在进行的获得结果的过程是如何工作的。

标签: c# asynchronous async-await


【解决方案1】:

那么我的周期性作业和 PutRecordBatchAsync 是同时处理的?

使用 Task API 可以确保它们同时执行(使用线程池),但您需要了解内存并发操作与基于 IO 的并发之间的区别。

虽然在内存中并发使用 Tasks 确实有好处,但 IO 调用一旦执行就根本不需要线程,因为它依赖于硬件并发,如果它曾经使用过线程,它会等待 IO 调用返回,从而浪费了宝贵的系统资源,降低了系统的可扩展性

您的情况是基于 IO 的并发,当您调用基于远程/网络的 API 时,async-await 对此有何帮助?

嗯,真正的异步操作会释放线程上下文,在windows上它会使用IO完成端口(排队机制)来执行异步调用,而调用线程用于调度其他类似的调用,它只需要线程上下文在返回 IO 调用以提供响应时,如果它不是 UI 调用,则使用 ConfigureAwait(false),以便可以使用任何线程上下文来传递响应。

如果你不使用 await 和 async 怎么办?

本来应该是异步的调用变成了同步的,并且会立即影响系统的可伸缩性,因为线程现在被阻塞了,对于长时间运行的 IO 操作来说更糟。您是否看到 JavaScript 框架总是对服务器 API 进行 AJAX(异步)调用,因此可以在不阻塞浏览器线程的情况下进行更多工作。

一般而言,对于内存处理,您将创建一定数量的任务并使用 Task.WaitAllParallel.ForEach 处理它们以用于集合,对于异步处理,理想情况下建议不要在任何地方使用 Task.Run,它首选必须从入口点Async,就像在 MVC 的情况下可能一样,控制器可以是异步的。多个呼叫使用Task.WhenAll 代表任务组合在一起,然后等待。即使您在代码中使用Task.Run,也请使用async lambda 执行异步调用

总结:

它必须使用await 进行异步调用,否则async 关键字在该上下文中是无用的,是的await 将在继续执行之前等待 IO 调用返回,尽管没有线程被阻塞在进程

【讨论】:

  • “本来是异步的调用变成了同步的,并且会立即影响系统的可伸缩性,因为线程现在被阻塞了”——我认为这是不正确的或不清楚的。是的,对 async 函数的调用是同步返回的,但从概念上讲,它总是这样;异步性“发生”在 await 语句中。如果 await 不存在,则调用者无序地通过异步函数。如果任务有一个延续,它仍然运行,但实际上是无头的;结果和异常被忽略。
  • (1/3)让我们在正确的上下文中理解语句,IO调用异步处理的本质是将处理转移到后台W/o阻塞任何等待后台进程的进程线程完成,从而确保更高的可伸缩性,因为在 Windows 中,每次执行都发生在一个线程上,并且非常不希望空闲等待。 await 所做的是创建一个状态机,它捕获当前的执行上下文,用于后续返回。在方法调用链中,实际异步处理在调用离开进程边界时开始
  • (2/3) 在此之前,我们希望每次给我的电话都标有async-await,而实际上它们仍然在等待在后台转移或使用基于硬件的并发的线程上运行通过网络拨打电话。现在在这里,如果我们在任何时候打破async-await,那么收益将不会出现,这就是为什么它必须从入口点开始(主要方法)。 Task continuation 与使用线程池线程不同,它不是纯异步的。异步处理的唯一其他用例是释放 UI 线程,但仍使用线程池线程进行逻辑处理
  • 我不明白你说的“好处”会丢失。如果我们知道不需要等待该方法,我们就不会因为不等待而降低可伸缩性。请注意,问题是关于异步方法,而不是异步调用。如果异步方法取出垃圾并保证在不到一个小时内完成,并且返回值是垃圾被取出时的气味,那么每周一次“解雇并忘记”任务是完全可以接受的,如果我们认为它符合我们的要求。
  • @MrinalKamboj 不使用 ThreadPool,除非您使用 Task.Run()。删除await在当前线程上启动异步方法。忽略所有结果(包括异常)。 - 见这里:stackoverflow.com/a/46245804/251267
【解决方案2】:

如果一个方法返回一个Task,最好在某个时候观察Task 的结果。它可能是声明的返回值,也可能是异常。如果您想在方法继续之前观察该任务,建议使用await

在这种情况下,您可能应该观察PutRecordBatchAsync 返回的Task 的结果。您将想知道调用是否因任何原因而失败,因为这可能表明您的记录没有被存储!

在您提供的示例代码中,您会发现您在上一次调用完成之前对MergeAndPutRecords 进行了后续调用。你确定这是故意的吗?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-06
    • 1970-01-01
    • 2018-03-11
    • 1970-01-01
    相关资源
    最近更新 更多