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

C# 不等待异步调用仍然是异步的,对吗?,c#,.net,asynchronous,async-await,C#,.net,Asynchronous,Async Await,如果这是一个愚蠢的问题(或重复的问题),我很抱歉 我有一个函数a: public async Task<int> A(/* some parameters */) { var result = await SomeOtherFuncAsync(/* some other parameters */); return (result); } 请注意,B未声明为async,也未等待调用A。对A的调用不是一个fire-and-forget调用-B由C调用,如下所示: p

如果这是一个愚蠢的问题(或重复的问题),我很抱歉

我有一个函数
a

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

    return (result);
}
请注意,
B
未声明为
async
,也未等待调用
A
。对
A
的调用不是一个fire-and-forget调用-
B
C
调用,如下所示:

public async Task C()
{
    await B(/* parameters */);
}
请注意,在#1,没有
等待
。我有一位同事声称,这使得调用
a
同步,他不断提出
控制台。WriteLine
日志似乎证明了他的观点

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


我的同事非常坚持,我开始怀疑自己。我的理解错了吗?你是对的。创建一个任务只会做到这一点,它不关心何时以及谁会等待它的结果。尝试放置
等待任务。延迟(veryBigNumber)SomeOtherFuncAsync
中使用code>,控制台输出应该是您所期望的

这就是所谓的省略,我建议你阅读,在那里你可以看到为什么你应该或不应该做这样的事情

还有一些最小的(有点复杂的)示例复制您的代码来证明您的正确性:

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}");
        }
    }
这将在第三个
结尾之前写入主的中间,证明它实际上是异步的。此外,您还可以(很可能)看到,函数的结尾与程序的其余部分在不同的线程上运行。main的开头和中间总是在同一个线程上运行,因为它们实际上是同步的(main启动,调用函数链,第三个返回(它可能在带有
wait
关键字的行中返回)然后main继续,就好像没有异步函数一样。这两个函数中的
wait
关键字后面的结尾可能会在线程池中的任何线程上运行(或在您正在使用的同步上下文中)

现在值得注意的是,如果
Third
中的
Task.Delay
不需要很长时间,并且实际上是同步完成的,那么所有这些都将在单个线程上运行。而且,即使它将异步运行,它也可能在单个线程上运行。没有规则规定异步函数我使用多个线程,它可能只是在等待I/O任务完成时做一些其他工作

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

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

我有一位同事声称,这会调用一个同步程序,他不断地提出Console.WriteLine日志,似乎证明了他的观点

这是最基本的问题,你需要教育你的同事,让他们停止误导自己和他人。没有异步调用这回事。调用不是异步的,永远都是。跟我说。调用在C中不是异步的。在C中,调用函数时,在计算所有参数后立即调用该函数

如果您的同事或您认为存在异步调用这样的事情,那么您将陷入一个痛苦的世界,因为您对异步如何工作的信念将与现实脱节

那么,你的同事是对的吗?他们当然是对的。
A
的调用是同步的,因为所有函数调用都是同步的。但是事实上,他们认为存在“异步调用”这一事实意味着他们对异步在C#中的工作原理大错特错

如果你的同事特别认为
await M()
以某种方式调用了
M()
“异步”,那么你的同事就有一个很大的误解。
await
是一个运算符。可以肯定,它是一个复杂的运算符,但它是一个运算符,它对值进行操作。
await M()
var t=M();wait t;
是相同的事情。wait发生在调用之后,因为
wait
对返回的值进行操作。
wait
不是编译器的指令,用于“生成对M()的异步调用”或任何类似的事情;没有“异步调用”这样的事情

如果这就是他们错误信念的本质,那么你就有机会教育你的同事,让他明白什么是
await
意思。
await
意味着简单但有力的东西。它意味着:

  • 看看我正在操作的
    任务
  • 如果任务异常完成,则抛出该异常
  • 如果任务正常完成,则提取该值并使用它
  • 如果任务不完整,请将此方法的剩余部分注册为等待任务的继续部分,并向我的调用者返回表示此调用的不完整异步工作流的新
    task
这就是
wait
所做的一切。它只检查任务的内容,如果任务不完整,它会说“好吧,在任务完成之前,我们无法在此工作流上取得任何进展,所以请返回给我的调用者,他会为CPU找到其他事情来做”

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

这是正确的。我们同步调用
A
,它返回一个
任务
。调用站点后的代码直到
A
返回才运行。
A
的有趣之处在于
A
允许
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}");
        }
    }