c# - 不等待异步调用仍然是异步的,对吗?




.net asynchronous (2)

如果这是一个愚蠢的问题,我很抱歉

这不是一个愚蠢的问题。 这是一个重要的问题。

我有一个同事声称这使对A的调用是同步的,并且他不断提出Console.WriteLine日志,这似乎证明了他的观点。

那是那里的根本问题,您需要教育您的同事,以免他们误导自己和他人。 没有异步调用之类的东西 调用 永远 不是异步 。 跟我说 调用在C#中不是异步的 。 在C#中,当您调用函数时, 在计算完所有参数后立即 调用该函数。

如果您的同事或您认为存在异步调用之类的事情,那么您就痛苦不堪,因为您对异步工作方式的信念将与现实脱节。

那么,您的同事正确吗? 当然是。 A 的调用是同步的,因为所有函数调用都是同步的 。 但是,他们相信存在诸如“异步调用”之类的事实,这意味着他们对C#中异步的工作方式有严重的误解。

如果您的同事特别认为 await M() 以某种方式使对 M() 的调用“异步”,则您的同事会产生很大的误解。 await 是一个 运算符 。 可以肯定,它是一个复杂的运算符,但它是一个运算符,并且对值进行运算。 await M()var t = M(); await t; var t = M(); await t; 一回事 。 等待发生 调用 之后 ,因为 await 对返回的值进行操作 await 不是 向编译器发出“生成对M()的异步调用”或诸如此类的指令。 没有所谓的“异步调用”。

如果这是他们错误信念的本质,那么您就有机会教育您的同事有关 await 含义。 await 意味着简单但功能强大。 它的意思是:

  • 查看我正在执行的 Task
  • 如果任务异常完成,则抛出该异常
  • 如果任务正常完成,请提取该值并使用它
  • 如果任务不完整,请将该方法的其余部分注册为等待的任务的继续,然后将表示 此调用 不完整的异步工作流的 Task 返回给 我的调用者

那就是 await 的一切。 它只是检查任务的内容,如果任务不完整,它会说:“好吧,在该任务完成之前,我们无法在此工作流程上取得任何进展,因此请返回给我的调用方,它将为该CPU找到其他内容去做”。

A中代码的性质不会因为我们不等待而改变。

没错 我们同步调用 A ,它返回一个 Task 。 直到 A 返回,呼叫站点之后的代码才运行。 关于 A 的有趣的事情是, 允许 A 向其调用者返回一个不完整的 Task ,并且该任务代表 异步工作流中的一个节点 。 工作流 已经 是异步的,并且正如您所注意到的,它对 A 的返回值没有影响。 A 不知道您是否要 await 返回的 TaskA 尽可能长时间地运行,然后要么返回正常完成的任务,要么异常完成的任务,或者返回不完整的任务。 但是您在呼叫站点上所做的任何事情都无法改变这一点。

由于不需要A的返回值,因此无需在呼叫站点等待任务

正确。

只要链上的某人等待任务(在C中发生),就无需在调用站点等待任务。

现在你失去了我。 为什么有人 必须 等待 A 返回的 Task 说出为什么您认为有人 需要 awaitTask ,因为您可能有错误的信念。

我的同事非常执着,我开始怀疑自己。 我的理解错了吗?

您的同事几乎可以肯定是错误的。 您的分析似乎是正确的,直到您说需要 await 每个 Task ,这是不正确的。 不 await Task 很奇怪的 ,因为这意味着您在开始操作时就编写了一个程序,并不关心它何时或如何完成,并且编写这样的程序肯定 很不好 ,但是没有 要求 await 每个 Task 。 如果您相信存在,请再说一次该信念,我们将对其进行整理。

抱歉,这是一个愚蠢的问题(或重复的问题)。

我有一个功能 A

public async Task<int> A(/* some parameters */)
{
    var result = await SomeOtherFuncAsync(/* some other parameters */);

    return (result);
}

我有另一个函数 B ,调用 A 但不使用返回值:

public Task B(/* some parameters */)
{
    var taskA = A(/* parameters */); // #1

    return (taskA);
}

请注意, B 没有被声明为 async ,并且不等待对 A 的调用。 对 A 的呼叫不是“即发即弃”呼叫 BC 呼叫,如下所示:

public async Task C()
{
    await B(/* parameters */);
}

请注意,在 #1处 ,没有 await 。 我有一个同事声称这使对 A 的调用是同步的,并且他不断提出 Console.WriteLine 日志,这似乎证明了他的观点。

我试图指出,这是因为我们不等待 B 内部的结果,因此等待任务链 并且 A 内部的代码的性质也不会因为我们不等待而改变。 由于不需要 A 的返回值,因此只要 链中有人等待它 ,就不需要在调用站点等待任务(在 C 中发生)。

我的同事非常执着,我开始怀疑自己。 我的理解错了吗?


你是对的。 创建任务只是这样做,它并不关心何时以及谁将等待其结果。 尝试 await Task.Delay(veryBigNumber);SomeOtherFuncAsync ,控制台输出应该是您期望的。

这称为消除(eliding),我建议您阅读 此博客文章 ,在这里您可以了解为什么应该或不应该这样做。

还有一些复制代码的最小示例(证明有点麻烦):

class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine($"Start of main {Thread.CurrentThread.ManagedThreadId}");
            var task = First();
            Console.WriteLine($"Middle of main {Thread.CurrentThread.ManagedThreadId}");
            await task;
            Console.WriteLine($"End of main {Thread.CurrentThread.ManagedThreadId}");
        }

        static Task First()
        {
            return SecondAsync();
        }

        static async Task SecondAsync()
        {
            await ThirdAsync();
        }

        static async Task ThirdAsync()
        {
            Console.WriteLine($"Start of third {Thread.CurrentThread.ManagedThreadId}");
            await Task.Delay(1000);
            Console.WriteLine($"End of third {Thread.CurrentThread.ManagedThreadId}");
        }
    }

Middle of main End of third 之前写入 Middle of main ,证明它实际上是异步的。 此外,您可以(最有可能)看到函数的末端与程序的其余部分在不同的线程上运行。 main的开始和中间始终将在同一线程上运行,因为它们实际上是同步的(main开始,调用函数链,第三个返回(它可能在与 await 关键字一起返回的行)),然后main继续,就好像那里这两个函数中的 await 关键字后面的结尾都可以在ThreadPool中的任何线程上运行(或在您使用的同步上下文中)。

现在有趣的是,如果 Task.Delay 中的 Task.Delay 花费的时间不很长并且实际上是同步完成的,那么所有这些都将在单个线程上运行。 而且,即使它可以异步运行,也 可能 全部在单个线程上运行。 没有规则说明异步函数将使用多个线程,它很可能只是在等待某些I / O任务完成的同时做其他工作。





async-await