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.Waitawait 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 的六

async函数

async.map 不会在 nodejs 中调用回调

Promise原理讲解 async+await应用(异步回调解决方案)

Python 3.5新特性都有些什么

iOS开发关于GCD多线程方面的知识