Task.Start / Wait和Async / Await有什么区别?


206

我可能会丢失一些东西,但是这样做之间有什么区别:

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);
}

Answers:


395

我可能会缺少一些东西

你是。

Task.Wait和和有await task什么区别?

您从餐厅的服务员那里订购午餐。下订单后的片刻,一个朋友走进来,坐在您旁边,开始对话。现在,您有两个选择。在任务完成之前,您可以忽略您的朋友-您可以等到汤汁到达时再等待,而无需执行其他操作。或者您可以回复您的朋友,当您的朋友停止讲话时,服务员会带您汤。

Task.Wait直到任务完成才阻止-您忽略您的朋友,直到任务完成。await继续在消息队列中处理消息,当任务完成时,它使一条消息排队,该消息为“在等待后从您离开的地方提起”。您与朋友交谈,谈话中断时,汤就到了。


5
@ronag不,不是。如果等待一个Task耗时10 ms Task的线程实际上在线程上执行了10个小时的时间,从而阻塞了整个10个小时,您会如何想呢?
svick

62
@StrugglingCoder:await运算符除了评估其操作数然后执行任何操作,然后立即将任务返回给当前调用方。人们脑子里有了这个想法,那就是异步只能通过将工作分流到线程上来实现,但这是错误的。当烤面包机在烤面包机中时,您可以烹饪早餐并阅读本文,而无需雇用厨师观看烤面包机。人们说,好吧,在烤面包机内一定要有一条线-一个工人-但我向您保证,如果您看着烤面包机,那里没有小家伙在看着烤面包机。
埃里克·利珀特

11
@StrugglingCoder:那么,谁在做您要求的工作?也许另一个线程正在执行该工作,并且该线程已分配给CPU,所以实际上正在完成该工作。也许工作是由硬件完成的,根本没有线程。但可以肯定的是,您说的是,硬件中必须有一些线程。否。硬件位于线程级别以下。不需要线程!您可能会从Stephen Stepheny的文章“没有线程”中受益。
埃里克·利珀特

6
@StrugglingCoder:现在,有一个问题,假设正在完成异步工作,没有硬件,也没有其他线程。这怎么可能?好吧,假设您等待的是一系列Windows消息的排队,每个消息都需要一点工作?现在会发生什么?您将控制权返回到消息循环,它开始将消息从队列中拉出,每次都做一点工作,最后完成的工作是“执行任务的继续”。没有多余的线程!
埃里克·利珀特

8
@StrugglingCoder:现在,想想我刚才说的话。 您已经知道Windows是这样工作的。您执行一系列鼠标移动,按钮单击以及其他操作。消息被排队,依次处理,每条消息导致少量工作要做,当所有工作完成后,系统继续运行。一个线程上的异步不过是您已经习惯的方式:将大任务分解成几小部分,将它们排队,然后按某种顺序执行所有小部分。这些处决中的一些使其他工作排队,生活继续下去。一线!
埃里克·利珀特

121

为了演示Eric的答案,这里是一些代码:

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);
}

27
代码+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!”具有误导性。当按下带有t.Wait();in按钮单击事件处理程序的按钮时,ButtonClick()由于GUI被冻结且无响应,因此无法单击任何东西,然后在控制台中看到某些内容并更新标签“直到此任务完成”,即所有单击或与GUI的交互 在完成任务等待之前一直处于迷失状态
纳季·范宁ГеннадийВанин

2
我想Eric假设您对Task api有基本的了解。我看了一下代码,对自己说:“ t.Wait是的,它将在主线程上阻塞,直到任务完成为止。”
松饼人

50

此示例非常清楚地说明了差异。通过异步/等待,调用线程将不会阻塞并继续执行。

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的改进示例。


4
但是如果我这样做:new Task(DoAsTask).Start(); 而不是DoAsAsync(); 我得到同样的functionalety,那么,是的await的好处..
omriman12

1
根据您的建议,必须在其他地方评估任务的结果,可能是其他方法或lambda。async-await使异步代码更易于遵循。它只是语法增强器。
2016年

@Mas我不明白为什么程序结束是在A之后-开始执行某项操作。根据我的理解,关键字等待过程应立即转到主要上下文,然后再返回。

@JimmyJimm以我的理解Task.Factory.StartNew将启动一个新线程来运行DoSomethingThatTakesTime。因此,不能保证将首先执行程序结束还是A-起始内容。
RiaanDP '16

@JimmyJimm:我已经更新了示例以显示线程ID。如您所见,“ Program End”和“ A-Started something”在不同的线程上运行。因此,实际上顺序不是确定的。
马斯(Mas)

10

Wait(),将导致以同步方式运行潜在的异步代码。等待不会。

例如,您有一个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调用,因为它在等待命中后即被释放。然后另一个请求可以使用它,甚至更多。完成http调用(user1或user2)后,Thread1可以获取结果并返回到调用方(客户端)。Thread1用于多个任务。


9

在这个例子中,实际上没有太多。如果您正在等待一个在不同线程上返回的任务(例如WCF调用),或者将控制权交给了操作系统(例如文件IO),则等待将通过不阻塞线程来减少系统资源。


3

在上面的示例中,可以使用“ 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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.