Task.Start/Wait 和 Async/Await 有啥区别?
Posted
技术标签:
【中文标题】Task.Start/Wait 和 Async/Await 有啥区别?【英文标题】:What's the difference between Task.Start/Wait and Async/Await?Task.Start/Wait 和 Async/Await 有什么区别? 【发布时间】:2012-03-20 03:16:59 【问题描述】:我可能遗漏了一些东西,但做这件事有什么区别:
public void MyMethod()
Task t = Task.Factory.StartNew(DoSomethingThatTakesTime);
t.Wait();
UpdateLabelToSayItsComplete();
public async void MyMethod()
var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
await result;
UpdateLabelToSayItsComplete();
private void DoSomethingThatTakesTime()
Thread.Sleep(10000);
【问题讨论】:
【参考方案1】:我可能错过了什么
你是。
Task.Wait
和await task
有什么区别?
您从餐厅的服务员那里订购午餐。在您下订单后不久,一位朋友走进来并坐在您旁边并开始交谈。现在你有两个选择。你可以忽略你的朋友,直到任务完成——你可以等到汤上来,在等待的时候什么也不做。或者你可以回复你的朋友,当你的朋友停止说话时,服务员会给你端上汤。
Task.Wait
阻塞直到任务完成——你忽略你的朋友直到任务完成。 await
继续处理消息队列中的消息,当任务完成时,它会将一条消息排入队列,上面写着“在等待之后从你离开的地方继续”。你和你的朋友交谈,当谈话中断时,汤就来了。
【讨论】:
@ronag 不,不是。如果等待 10 毫秒的Task
实际上会在您的线程上执行长达 10 小时的 Task
,从而阻塞您整整 10 小时,您会怎么想?
@StrugglingCoder: await 运算符除了 评估其操作数然后立即将任务返回给当前调用者之外,不执行任何操作。人们在脑海中产生这样的想法,即异步只能通过将工作卸载到线程上来实现,但这是错误的。您可以在烤面包机中煮早餐并阅读报纸,而无需聘请厨师看烤面包机。人们说好吧,烤面包机里面一定有一个线程——一个工人——但是我向你保证,如果你看看你的烤面包机,里面没有小家伙在看烤面包。
@StrugglingCoder:那么,谁在做你问的工作?也许另一个线程正在做这项工作,并且该线程已被分配给一个 CPU,所以工作实际上正在完成。也许工作是由硬件完成的,根本没有线程。但是,您肯定会说,硬件中一定有某个线程。不,硬件存在于线程级别之下。不需要有线程!阅读 Stephen Cleary 的文章 There Is No Thread 可能会让您受益。
@StrugglingCoder:现在,问题,假设正在进行异步工作并且没有硬件,也没有其他线程。这怎么可能?好吧,假设您等待的事情排列一系列窗口消息,每个消息都有一点作用?现在会发生什么?您将控制权返回给消息循环,它开始将消息从队列中拉出,每次都做一些工作,最后完成的工作是“执行任务的延续”。没有多余的线程!
@StrugglingCoder:现在,想想我刚才说的话。 您已经知道 Windows 就是这样工作的。您执行一系列鼠标移动和按钮单击等。消息排队,依次处理,每条消息都会导致完成少量工作,当所有工作完成后,系统会继续运行。一个线程上的异步只不过是您已经习惯的:将大任务分解成小块,将它们排队,并以某种顺序执行所有小块。其中一些执行会导致 other 工作排队,生活还在继续。一个线程!【参考方案2】:
在这个例子中,实际上并不多。如果您正在等待在不同线程上返回的任务(如 WCF 调用)或将控制权交给操作系统(如文件 IO),则 await 将通过不阻塞线程来使用更少的系统资源。
【讨论】:
【参考方案3】:为了证明埃里克的答案,这里有一些代码:
public void ButtonClick(object sender, EventArgs e)
Task t = new Task.Factory.StartNew(DoSomethingThatTakesTime);
t.Wait();
//If you press Button2 now you won't see anything in the console
//until this task is complete and then the label will be updated!
UpdateLabelToSayItsComplete();
public async void ButtonClick(object sender, EventArgs e)
var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
await result;
//If you press Button2 now you will see stuff in the console and
//when the long method returns it will update the label!
UpdateLabelToSayItsComplete();
public void Button_2_Click(object sender, EventArgs e)
Console.WriteLine("Button 2 Clicked");
private void DoSomethingThatTakesTime()
Thread.Sleep(10000);
【讨论】:
代码+1(运行一次比阅读一百次更好)。但是“//If you press Button2 now you won't see anything in the console until this task is complete and then the label will be updated!
”这个短语具有误导性。在按钮单击事件处理程序ButtonClick()
中按下带有t.Wait();
的按钮后,无法按下任何内容,然后在控制台中看到某些内容并更新标签“直到此任务完成”,因为 GUI 已冻结且无响应,即任何与 GUI 的点击或交互都将丢失,直到任务等待完成
我猜 Eric 假设您对 Task api 有基本的了解。我看着那段代码对自己说“是的t.Wait
将阻塞主线程,直到任务完成。”【参考方案4】:
这个例子非常清楚地展示了差异。使用 async/await 调用线程不会阻塞并继续执行。
static void Main(string[] args)
WriteOutput("Program Begin");
// DoAsTask();
DoAsAsync();
WriteOutput("Program End");
Console.ReadLine();
static void DoAsTask()
WriteOutput("1 - Starting");
var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
WriteOutput("2 - Task started");
t.Wait();
WriteOutput("3 - Task completed with result: " + t.Result);
static async Task DoAsAsync()
WriteOutput("1 - Starting");
var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
WriteOutput("2 - Task started");
var result = await t;
WriteOutput("3 - Task completed with result: " + result);
static int DoSomethingThatTakesTime()
WriteOutput("A - Started something");
Thread.Sleep(1000);
WriteOutput("B - Completed something");
return 123;
static void WriteOutput(string message)
Console.WriteLine("[0] 1", Thread.CurrentThread.ManagedThreadId, message);
DoAsTask 输出:
[1] 节目开始 [1] 1 - 开始 [1] 2 - 任务开始 [3] A - 开始做某事 [3] B - 完成某事 [1] 3 - 任务完成,结果:123 [1] 程序结束DoAsAsync 输出:
[1] 节目开始 [1] 1 - 开始 [1] 2 - 任务开始 [3] A - 开始做某事 [1] 程序结束 [3] B - 完成某事 [3] 3 - 任务完成,结果:123更新:通过在输出中显示线程 ID 来改进示例。
【讨论】:
但如果我这样做:new Task(DoAsTask).Start();而不是 DoAsAsync();我得到了相同的功能,那么等待的好处在哪里.. 根据您的提议,任务的结果必须在其他地方进行评估,可能是另一种方法或 lambda。 async-await 使异步代码更易于遵循。它只是一个语法增强器。 @Mas 我不明白为什么程序结束在 A 之后 - 开始了一些事情。根据我的理解,当涉及到 await 关键字过程时,应该立即进入主上下文,然后返回。 @JimmyJimm 据我了解,Task.Factory.StartNew 将启动一个新线程来运行 DoSomethingThatTakesTime。因此,不能保证 Program End 或 A - Started Something 会先执行。 @JimmyJimm:我已更新示例以显示线程 ID。如您所见,“程序结束”和“A - Started something”在不同的线程上运行。所以实际上顺序不是确定性的。【参考方案5】:Wait(),将导致以同步方式运行潜在的异步代码。 await 不会。
例如,您有一个 asp.net Web 应用程序。 UserA 调用 /getUser/1 端点。 asp.net 应用程序池将从线程池 (Thread1) 中选择一个线程,并且该线程将进行 http 调用。如果你执行 Wait(),这个线程将被阻塞,直到 http 调用解决。在等待期间,如果 UserB 调用 /getUser/2,则应用程序池将需要为另一个线程(Thread2)提供服务以再次进行 http 调用。您刚刚无缘无故地创建了(实际上是从应用程序池中获取)另一个线程,因为您不能使用 Thread1 它被 Wait() 阻塞。
如果您在 Thread1 上使用 await,则 SyncContext 将管理 Thread1 和 http 调用之间的同步。简单地说,一旦 http 调用完成,它就会通知。同时,如果 UserB 调用 /getUser/2,那么,你将再次使用 Thread1 进行 http 调用,因为一旦 await 被命中,它就会被释放。然后另一个请求可以使用它,甚至更多。一旦 http 调用完成(user1 或 user2),Thread1 可以获取结果并返回给调用者(客户端)。 Thread1 用于多个任务。
【讨论】:
【参考方案6】:在上面的例子中,可以使用“TaskCreationOptions.HideScheduler”,并大大修改“DoAsTask”方法。该方法本身不是异步的,就像“DoAsAsync”一样,因为它返回一个“Task”值并标记为“async”,进行了多种组合,这就是它给我的方式与使用“async / await”完全相同:
static Task DoAsTask()
WriteOutput("1 - Starting");
var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime, TaskCreationOptions.HideScheduler); //<-- HideScheduler do the magic
TaskCompletionSource<int> tsc = new TaskCompletionSource<int>();
t.ContinueWith(tsk => tsc.TrySetResult(tsk.Result)); //<-- Set the result to the created Task
WriteOutput("2 - Task started");
tsc.Task.ContinueWith(tsk => WriteOutput("3 - Task completed with result: " + tsk.Result)); //<-- Complete the Task
return tsc.Task;
【讨论】:
以上是关于Task.Start/Wait 和 Async/Await 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章
JavaScript 的 Async/Await 完胜 Promise 的六