Is Async/Await using Task.Run starting a new thread asynchronously?
我读了很多文章,但还是不能理解这一部分。
考虑此代码:
1 2 3 4 5 6 7 8 9 10
| private async void button1_Click(object sender, EventArgs e)
{
await Dosomething();
}
private async Task<string> Dosomething()
{
await Task.Run((() =>"Do Work"));
return"I am done";
} |
第一个问题:
当我单击按钮时,它将调用dosomething,并等待通过调用task.run(如果我没有弄错)从线程池创建线程的任务,所有这些都将异步运行。所以我实现了创建一个线程来完成我的工作,但是异步地完成它?但是考虑到我不需要返回任何结果,我只是希望在不返回任何结果的情况下完成工作,是否确实需要使用异步/等待,如果需要,如何进行?
第二个问题:
异步运行线程时,如何工作?它是在主UI上运行,但在一个单独的线程上运行,还是在一个单独的线程上运行,而separate则在该方法内部异步运行?
- 我还不太熟悉async/await,但我相信是否使用新线程取决于操作系统的决定——即,不能保证在新线程上执行。
- @TimNit:如果被告知这样做,操作系统将启动一个新线程。任何相关的线程池等都发生在.NET本身中。
- @用户2864740-感谢您的澄清:)
- async/await永远不会创建新的线程,否则我们都必须在等待调用之后为所有内容编写线程安全代码,这正是async await试图避免的。不管等待的方法本身是否产生一个新的线程async/await与之无关,等待的调用总是在调用它的同一个线程上运行。
- @markmnl-但是,任何阅读您在这里的评论的人也应该阅读您和matias在其答案下的讨论,其中澄清了等待完成后的继续可能位于不同的线程上,这取决于当前的同步上下文。幸运的是,对于一个UI线程,上下文确实确保返回到该UI线程。对于用户界面来说,它完全是一个线程。[我的意思是,如果是await,但不要是Task.Run。]
创建异步方法的目的是让您以后可以等待它们。有点像"我要把这水烧开,把剩下的汤料准备好,然后回到锅里等水烧完,这样我就可以做晚饭了。"你开始把水烧开,当你做其他事情的时候,它是异步的,但最终你必须停下来等它。如果你想要的是"发射并忘记",那么异步和等待是不必要的。
在C中使用最简单的方法来进行火和忘记方法。
启动新任务会将该任务排队,以便在线程池线程上执行。线程在进程上下文中执行(例如,运行应用程序的可执行文件)。如果这是在IIS下运行的Web应用程序,则该线程是在IIS工作进程的上下文中创建的。该线程与主执行线程分开执行,因此无论主执行线程在做什么,它都会关闭并执行它的事情,同时,主执行线程会继续执行自己的工作。
- 谢谢你的回答。因此,使用"wait task.run((()=>"do work");"的最大原因之一是,我想启动一个执行某项任务,并等待它完成,然后再继续下一句代码。我在这儿吗?那么,在执行"wait task.run((()=>"do work"));"时,它是否只启动1个线程,或者启动2个线程?
- 没错。wait的意思是"在异步任务完成之前停止执行",并在异步线程完成之前阻止调用线程。我在现实生活中使用过它来缓存应用程序启动时来自Web服务的数据。应用程序将触发对不同服务的5个异步调用,然后等待它们全部返回,然后继续。这允许所有调用同时发生,但在它们全部完成之前仍然会阻塞。无论您使用的是async/await还是task.run,任务都是在单独的线程池线程上启动的。
- 我知道你在问关于线程数量的问题。无论您使用task.run还是wait,它都将启动一个线程。wait只是告诉调用线程阻塞并监视任务线程,直到它完成。
- 我能理解的最简单的问题答案,非常感谢。
- @dvk await不阻塞当前线程。async/await as关键字与并发无关。wait只需检查任务是否完成。如果是,执行继续进行。如果不是,该方法会将未完成的任务返回到调用堆栈中的上一个方法。在等待异步操作完成时,异步/等待点之一不必阻塞当前线程。
- @除非你提到我的评论,否则我没有提到任何关于异步/等待阻塞线程的内容。我说过使用async/await的目的是让您可以启动一个异步任务,做一些事情,然后等待该任务(或者许多任务,或者其他任何任务)。如果我没有记错,如果您没有等待一个异步任务,您将得到一个编译器警告。如果你不想在任何时候阻塞(例如,开火和忘记),还有其他的机制。
- @dvk我指的是你的评论,你声称await阻塞了当前线程,暗示async或await与线程有任何关系,await甚至生成线程,等等。所有这些都是事实上的错误。
- @dvk-re"我要把水烧开,把剩下的汤料准备好,然后回来……"这种描述有可能让人认为await+async足以同时执行。在问题的上下文中,尽管UI线程没有被阻塞[另一个UI事件可能会出现并开始执行],但是您处于中间的"配方"在等待的操作完成之前不会继续。我的意思是,如果button1_click有第二个声明(准备配料),那么在DoSomething运行时就不会这样做。
类型Task要求您从任务中返回TResult。如果您没有要返回的内容,可以使用Task代替(顺便说一下,它是Task的基类)。
但请记住,任务不是线程。任务是要完成的任务,而线程是工作线程。随着程序的运行,工作和工作人员变得可用和不可用。在幕后,库将把您的作业分配给可用的工人,并且,由于创建新工人是一个昂贵的操作,所以它通常更喜欢通过线程池重用现有的工人。
- 谢谢你的回答。但是在线程池上的工作线程上启动一个新任务实际上会使用一个不同于UI线程的线程。我真的看不到使用异步/等待的更多原因,而不是在我完成下一句代码之前,我想启动一个任务并等待它完成。:
- @Syncis有什么问题吗?
一
如果你不使用await和Task或await的话,有很大的区别:
因此,async和await的需求将取决于你如何称呼DoSomething:如果你不称呼await,就像称呼它为火与忘记的方式。
二
Is it running on the main UI but on a separate thread or is it running
on a seperate thread and separate is asynchronously inside that
method?
异步代码有时意味着其他线程(参见本问题和异步与多线程——有区别吗?)。也就是说,如果代码是在与用户界面线程分离的线程中执行的,或者它允许用户界面线程在恢复时继续处理,这很好,因为用户界面循环仍然可以在其他任务并行执行时更新屏幕,而不会冻结用户界面。
异步方法(即async方法)是一种句法上的制糖,用来告诉编译器await语句应被视为状态机。C编译器将您的async/await代码转换为状态机,在该状态机中等待Task结果的代码在等待的代码之后执行。
有趣的问答
您可能希望查看以下其他问题:
- 异步/等待vs线程
- task.start/wait和async/wait有什么区别?
- 异步/等待-什么时候返回任务vs void?
- async await关键字是否等效于具有lambda的continuewWith?
奥普说…
[...] But does this mean that"async/await" will fire off a thread and
Task.Run also fires off a thread or are they both the same thread?
使用async-await并不意味着"我创建了一个线程"。以优雅的方式实现延续只是一种句法上的甜头。任务可以是线程,也可以不是线程。例如,Task.FromResult(true)创建了一个假任务,以便能够实现异步方法而无需创建线程:
1 2 3 4 5 6
| public Task<bool> SomeAsync()
{
// This way, this method either decides if its code is asynchronous or
// synchronous, but the caller can await it anyway!
return Task.FromResult(true);
} |
- 谢谢你的回答。但这是否意味着"async/await"将触发线程和任务。run也将触发线程,还是它们都是同一线程?
- @SyncisAsyncWait并不意味着"我创建了一个线程"。以优雅的方式实现延续只是一种句法上的甜头。Task可以是线程,也不能是线程。例如,Task.FromResult(true)创建了一个假任务,以便能够实现异步方法而无需创建线程。
- @同步检查更新"op said…"在答案本身。
- async/await从不创建新线程-它在从其调用的同一线程上执行延续。所调用的awaitable方法是否在新线程上执行async await被忽略
- @Markmnl所以这就是我的答案……看OP的部分。除了某些绝对错误的事情:Async/Await从不创建线程,但当执行被挂起时,它总是在同一线程上继续是不正确的。
- @马克,顺便说一句,我的答案里还有一件事。异步并不总是意味着生成一个新线程。
- @mat&237;asfidmraizer async await从不在其他线程上恢复,如果awaitable方法生成一个新线程并在后台运行某个线程,则返回对调用方来说不重要-关键是当它返回调用方在调用它的同一线程上恢复时。这就是异步等待的全部要点——调用者没有WIRTE多线程代码。
- @Mat&237;Fidelmraizer也许我们说的是同一件事,但我发现你的回答令人费解和困惑。例如,在UI示例中,我们知道等待的调用总是在同一线程(UI线程)上继续,否则我们无法使用来自UI线程的异步等待,除非在等待返回在同一线程上继续执行之后得到保证。
- @对于ASP.NET WebAPI/MVC核心请求处理,markmnl是否为真?
- @Mat&237;作为Fidelmraizer,我现在看到它不一定取决于当前上下文,我坚持更正:)stackoverflow.com/questions/21838651/&hellip;
- @markmnl yup,实际上我不是本地桌面/移动开发人员,我主要是框架、后端和Web开发人员。我在winforms和wpf上都工作过,但我已经离开它们很长时间了(而且可能永远离开)。我真的不知道await在UI线程中恢复了,我知道您不再需要手动将另一个线程与UI线程同步,对吧?
- @markmnl在asp.net webapi/mvc核心中,在另一个线程池线程上恢复await是一个很大的优势,尽管ui世界中提供了简单的功能(正如您之前指出的那样),但是任何具有共享资源的代码在默认情况下都应该是线程安全的,因为否则它在wpf中可能工作得很好,但是它可能导致一个复杂的ASP.NET WebAPI中的ETE灾难。
- @markmnl结论:我们需要使用async--await,就好像它永远不会在暂停操作的同一线程上恢复一样。
- @Mat&237;作为Fidelmraizer否,我不会这么说,我们可以安全地假设,当在UI线程上它将在UI线程上恢复时,否则我们将不得不将所有内容封送回UI线程。另外,在延续中必须有一些线程安全同步,因为我们似乎不必担心在延续中使所有的线程都安全,如果它只是在新线程上运行而没有任何同步,那么我们就可以这样做。
- @markmnl btw我的意思是,您不会得出这样的结论,即它本身将在调用方的线程上恢复。它取决于上下文/API。因此,如果有人问我这个问题,我会回答:"不,这取决于SynchronizationContext"