Mat*_*ias 14 c# asynchronous async-await
我在网上搜索过,看过很多关于task.run和等待异步的问题,但是有一个特定的使用场景,我并不是真的了解它的区别.我相信情景非常简单.
await Task.Run(() => LongProcess());
Run Code Online (Sandbox Code Playgroud)
VS
await LongProcess());
Run Code Online (Sandbox Code Playgroud)
其中LongProcess是一个异步方法,其中包含一些异步调用,例如调用db并等待ExecuteReaderAsync().
题:
这种情况下两者之间有什么区别吗?感谢任何帮助或意见,谢谢!
The*_*ias 14
这个答案涉及在 GUI 应用程序的事件处理程序中等待异步方法的特定情况。在这种情况下,第一种方法比第二种方法具有显着的优势。在解释原因之前,让我们以清楚反映该答案上下文的方式重写这两种方法。以下内容仅与 GUI 应用程序的事件处理程序相关。
\nprivate async void Button1_Click(object sender, EventArgs args)\n{\n await Task.Run(async () => await LongProcessAsync());\n}\nRun Code Online (Sandbox Code Playgroud)\n与
\nprivate async void Button1_Click(object sender, EventArgs args)\n{\n await LongProcessAsync();\n}\nRun Code Online (Sandbox Code Playgroud)\nAsync我在方法名称中添加了后缀,以符合指南。出于可读性的原因,我还对匿名委托进行了异步处理。Task.Run创建状态机的开销是微乎其微的,并且与清楚地传达这会返回一个承诺风格的价值相比相形见绌,而不是用于后台处理 CPU 密集型工作负载的Task老式委托。Task
第一种方法的优点是保证 UI 保持响应。第二种方法不提供这样的保证。只要您使用 .NET 平台的内置异步 API,UI 被第二种方法阻塞的可能性就很小。毕竟这些API都是由experts\xc2\xb9实现的。当您开始等待自己的异步方法时,所有保证都已关闭。当然,除非你的名字是斯蒂芬,姓氏是图布或克利里。如果情况并非如此,那么您很可能迟早会编写如下代码:
\npublic static async Task LongProcessAsync()\n{\n TeenyWeenyInitialization(); // Synchronous\n await SomeBuildInAsyncMethod().ConfigureAwait(false); // Asynchronous\n CalculateAndSave(); // Synchronous\n}\nRun Code Online (Sandbox Code Playgroud)\n问题显然出在方法上TeenyWeenyInitialization()。此方法是同步的,并且出现在异步方法体内的第一个方法之前await,因此不会等待它。每次调用时它都会同步运行LongProcessAsync()。因此,如果您遵循第二种方法(不使用Task.Run),它将TeenyWeenyInitialization() 在 UI 线程上运行。
这有多糟糕?毕竟初始化是很小的!只需快速访问数据库即可获取值,读取小文本文件的第一行,从注册表中获取值。一切都在几毫秒内结束。在你写程序的时候。在您的电脑中。将数据文件夹移动到共享驱动器之前。在数据库中的数据量变得巨大之前。
\n但你可能很幸运,并且TeenyWeenyInitialization()永远保持快速,那么第二个同步方法呢CalculateAndSave()?该线程位于await配置为不捕获上下文的线程之后,因此它在线程池线程上运行。它永远不应该在 UI 线程上运行,对吧?错误的。这取决于Task返回的SomeBuildInAsyncMethod(). 如果Task完成,则不会发生线程切换,并且将CalculateAndSave()在调用该方法的同一线程上运行。如果您遵循第二种方法,这将是 UI 线程。SomeBuildInAsyncMethod()您可能永远不会遇到在开发环境中返回已完成的情况Task,但生产环境可能会以难以预测的方式有所不同。
拥有一个性能不佳的应用程序是令人不愉快的。如果应用程序性能不佳并冻结 UI,那就更糟糕了。你真的想冒险吗?如果不这样做,请Task.Run(async在事件处理程序中使用always。尤其是在等待您自己编写的方法时!
\xc2\xb9免责声明,某些内置异步 API未正确实现。
\n重要提示:在线程上运行Task.Run提供的异步委托ThreadPool,因此要求LongProcessAsync与 UI 线程没有关联。如果它涉及与 UI 控件的交互,那么这Task.Run不是一个选项。感谢@Zmaster 在评论中指出了这一重要的微妙之处。
Har*_*lse 13
人们常常认为async-await是由几个线程完成的.事实上,这一切都是由一个线程完成的.
请参阅下面有关此一个线程语句的添加内容
帮助我理解async-await的事情是Eric Lippert对async-await的采访.在中间的某个地方,他比较异步等待与必须等待一些水沸腾的厨师.他没有做任何事情,而是环顾四周,看看还有别的东西要做,比如切洋葱.如果完成,水仍然没有沸腾,他会检查是否还有其他事情要做,等等,直到他无所事事,只能等待.在那种情况下,他回到他等待的第一件事.
如果你的过程调用了一个等待函数,我们可以肯定在这个等待函数的某个地方有一个等待函数的调用,否则函数将无法等待.实际上,如果您忘记等待等待函数中的某个地方,您的编译器会发出警告.
如果你的等待函数调用另一个等待函数,则线程进入另一个函数并开始执行此函数中的操作并深入到其他函数,直到他遇到await.
线程在他的调用堆栈中上升,而不是等待结果,看看是否有其他代码可以处理,直到他看到等待.再次进入调用堆栈,进行处理直到等待等等.一旦每个人都在等待线程查找底部等待并继续一旦完成.
这样做的好处是,如果等待函数的调用者不需要函数的结果,但是在需要结果之前可以做其他事情,那么这些其他的事情可以由线程完成,而不是在函数内部等待.
没有立即等待结果的调用将如下所示:
private async Task MyFunction()
{
Task<ReturnType>taskA = SomeFunctionAsync(...)
// I don't need the result yet, I can do something else
DoSomethingElse();
// now I need the result of SomeFunctionAsync, await for it:
ReturnType result = await TaskA;
// now you can use object result
}
Run Code Online (Sandbox Code Playgroud)
请注意,在这种情况下,一切都由一个线程完成.只要你的线程有事可做,他就会很忙.
加成.事实上只涉及一个线程.任何无所事事的线程都可能在等待之后继续处理您的代码.如果检查线程ID,则可以看到在等待之后可以更改此ID.连续的线程
context与原始线程相同,因此您可以将其视为原始线程.无需检查InvokeRequired,无需使用互斥锁或关键部分.对于您的代码,这就好像涉及一个线程.
本回答末尾的文章链接解释了有关线程上下文的更多信息
您将看到等待其他进程必须执行操作的等待函数,而您的线程只需等待,直到另一个进程完成.例如,通过互联网发送数据,保存文件,与数据库通信等.
但是,有时需要进行一些繁重的计算,并且您希望您的线程可以自由地执行其他操作,例如响应用户输入.在该桶中,您可以启动等待操作,就像调用异步函数一样.
Task<ResultType> LetSomeoneDoHeavyCalculations(...)
{
DoSomePreparations()
// start a different thread that does the heavy calculations:
var myTask = Task.Run( () => DoHeavyCalculations(...))
// now you are free to do other things
DoSomethingElse();
// once you need the result of the HeavyCalculations await for it
var myResult = await myTask;
// use myResult
...
}
Run Code Online (Sandbox Code Playgroud)
现在一个不同的线程正在进行繁重的计算,而你的线程可以自由地做其他事情.一旦它开始等待你的来电者可以做的事情,直到他开始等待.有效地,您的线程可以相当自由地对用户输入作出反应.但是,只有每个人都在等待,才会出现这种情况.当您的线程忙于处理时,您的线程无法对用户输入作出反应.因此,如果您认为您的UI线程必须执行一些繁忙的处理需要一些时间使用Task.Run并让另一个线程执行它,请务必确保
另一篇帮助我的文章:Async-Await由出色的解释者Stephen Cleary提供
Lua*_*aan 12
Task.Run 可以在不同的线程上发布要处理的操作.这是唯一的区别.
这可能是有用的 - 例如,如果LongProcess不是真正的异步,它将使调用者返回更快.但对于一个真正的异步方法,使用没有意义Task.Run,它可能会导致不必要的浪费.
但要小心,因为行为Task.Run将根据重载决议而改变.在您的示例中,Func<Task>将选择重载,这将(正确)等待LongProcess完成.但是,如果使用了非任务返回的委托,Task.Run则只会等到执行第一个委托await(请注意,这TaskFactory.StartNew将始终如一,所以不要使用它).