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

xxb*_*bcc 17 .net c# asynchronous async-await

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

我有一个功能A

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

    return (result);
}
Run Code Online (Sandbox Code Playgroud)

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

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

    return (taskA);
}
Run Code Online (Sandbox Code Playgroud)

请注意,B没有声明async,也没有等待对的调用A。对的呼叫A不是“一劳永逸”的呼叫- B的调用方式C如下:

public async Task C()
{
    await B(/* parameters */);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Eri*_*ert 28

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

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

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

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

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

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

如果您的同事特别认为以await M()某种方式发出了M()“异步” 的呼吁,那么您的同事就会产生很大的误解。 await运算符。可以肯定,它是一个复杂的运算符,但它是一个运算符,并且对值进行运算。 await M()而且var t = M(); await t;同一回事。等待操作发生调用之后,因为await 操作返回的值awaitNOT到编译器“产生到M()异步调用”或任何这样的事情的指令; 没有所谓的“异步调用”。

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

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

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

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

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

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

正确。

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

现在你失去了我。为什么有人必须等待Task归还者A说为什么你认为有人需要await那个Task,是因为你可能有一个错误的信念。

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

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

  • @GabrielLuci 我认为在日常讨论中很难有意识地区分“调用”和“工作流程” - 我认为 Eric 与编译器的历史使​​他更容易这样做。我经常谈论“异步调用”,尽管我了解语句如何在函数中一次执行一个。(为了精确起见,我不得不重写这条评论 3 次,但我仍然不确定我写了我想说的内容。) (2认同)

Ord*_*sen 6

你是对的。创建任务只是这样做,它并不关心何时以及谁将等待其结果。尝试放入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}");
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

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