很抱歉,这是一个愚蠢的问题(或重复的问题)。
我有一个函数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
的呼叫不是一劳永逸的呼叫-B
呼叫C
就像这样:
public async Task C()
{
await B(/* parameters */);
}
请注意,在#1 ,没有await
。我有一个同事声称,这使得对A
的调用是同步的,他不断提出Console.WriteLine
日志,似乎证明了他的观点。
我试图指出,只是因为我们不等待B
中的结果,所以等待了任务链 ,而A
中的代码本质却没有不会因为我们不等待而改变。由于不需要A
的返回值,因此只要链中有人等待它,就无需在调用站点等待任务。(在{{1}中发生}。
我的同事非常执着,我开始怀疑自己。我的理解错了吗?
答案 0 :(得分:28)
很抱歉,这是一个愚蠢的问题
这不是一个愚蠢的问题。这是一个重要的问题。
我有一个同事声称这使对A的调用是同步的,并且他不断提出Console.WriteLine日志,这似乎证明了他的观点。
那是当前的基本问题,您需要教育您的同事,以免他们误导自己和他人。 没有异步调用之类的东西。 通话不是异步的,永远。跟我说在C#中调用不是异步的。在C#中,调用函数时,在计算完所有参数后立即调用该函数。
如果您的同事或您认为存在异步调用之类的事情,那么您就痛苦不堪,因为您对异步工作方式的信念将与现实脱节。
那么,您的同事正确吗?当然是。 由于所有函数调用都是同步的,因此对A
的调用是同步的。但是,他们相信存在“异步调用”之类的事实,这意味着他们对C#中异步的工作方式有严重的误解。
如果您的同事特别认为await M()
以某种方式使M()
的调用“异步”,那么您的同事会产生很大的误解。 await
是运算符。可以肯定,它是一个复杂的运算符,但它是一个运算符,并且对值进行运算。 await M()
和var t = M(); await t;
是同一件事。等待发生在调用之后 ,因为await
对返回的值进行操作。 await
是不是的指令,指示编译器“生成对M()的异步调用”或任何此类事物;没有所谓的“异步调用”。
如果这是他们错误信念的本质,那么您就有机会教育您的同事await
的含义。 await
意味着简单但功能强大。这意味着:
Task
。 Task
我的呼叫者的异步工作流程不完整。这就是await
的全部工作。它只是检查任务的内容,如果任务不完整,它会说:“好吧,在该任务完成之前,我们无法在此工作流程上取得任何进展,因此请返回给我的调用方,它将为该CPU找到其他内容要做”。
A中代码的性质不会因为我们不等待而改变。
是的。我们同步调用A
,它返回一个Task
。直到A
返回,呼叫站点之后的代码才会运行。关于A
的有趣之处在于,允许 A
向其调用者返回不完整的Task
,并且该任务表示异步节点工作流程。工作流已经是 异步的,并且您注意到,它对A
的返回值 返回后的操作没有影响。 A
不知道您是否要await
返回的Task
。 A
会一直运行,然后返回正常完成的任务或异常完成的任务,或者返回不完整的任务。但是您在呼叫站点上所做的任何事情都无法改变这一点。
由于不需要A的返回值,因此无需在呼叫站点等待任务
正确。
只要链上的某人等待任务(在C中发生),就无需在呼叫站点等待任务。
现在你迷失了我。为什么有人必须等待Task
返回的A
? 说出为什么您认为某人需要{em> await
到Task
,因为您可能有错误的信念。
我的同事非常执着,我开始怀疑自己。我的理解错了吗?
您的同事几乎可以肯定是错误的。您的分析似乎是正确的,直到您说有一个要求,要求每个Task
都要await
,这是不正确的。并非await
一个Task
是奇怪的,因为这意味着您编写的程序是在其中开始操作的,并不关心何时或如何完成,当然闻起来很糟糕编写这样的程序,但是每个await
都没有{em>要求。如果您再次相信存在,请说出该信念是什么,我们将对其进行整理。
答案 1 :(得分:6)
你是对的。创建任务只是这样做,它并不关心何时以及谁将等待其结果。尝试将await Task.Delay(veryBigNumber);
放在SomeOtherFuncAsync
中,控制台输出应该是您期望的。
这被称为淘汰,我建议您阅读this blogpost,在这里您可以了解为什么应该或不应该这样做。
还有一些最小的(有点麻烦的)示例复制了代码,证明您是正确的:
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
之前,证明它实际上是异步的。此外,您可以(最有可能)看到函数的末端与程序的其余部分在不同的线程上运行。 main的开始和中间始终将在同一线程上运行,因为实际上它们是同步的(main开始,调用函数链,第三次返回(它可能在与await
关键字一起返回),然后是main继续,就好像没有涉及异步函数一样。两个函数中await
关键字之后的结尾可以在ThreadPool中的任何线程上运行(或在您使用的同步上下文中)。
现在值得注意的是,如果Task.Delay
中的Third
花费的时间并不很长,并且实际上是同步完成的,那么所有这些都将在单个线程上运行。而且,即使它可以异步运行,它也可能都在单个线程上运行。没有规则说明异步函数将使用多个线程,它很可能在等待一些I / O任务完成的同时做其他工作。