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

Jon*_*Jon 204 c# conceptual task-parallel-library async-await .net-4.5

我可能会遗漏一些东西但是做什么之间有什么不同:

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);
}
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 389

我可能会遗漏一些东西

你是.

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

你在餐厅的服务员点了午餐.在给出订单后的一刻,一位朋友走进来,坐在你身边,开始交谈.现在你有两个选择.你可以忽略你的朋友,直到任务完成 - 你可以等到你的汤到来,在你等待的时候什么都不做.或者你可以回复你的朋友,当你的朋友停止说话时,服务员会给你带来汤.

Task.Wait阻止任务完成 - 在任务完成之前,您将忽略您的朋友.await继续处理消息队列中的消息,当任务完成时,它会将一条消息列入"在等待之后从中断处继续".你和你的朋友谈话,当谈话休息时,汤就到了.

  • @StrugglingCoder:*await*运算符不*执行*除**以外的任何事情评估其操作数,然后立即将任务返回给当前调用者**.人们在脑海中得到这样的想法,即只能通过将工作卸载到线程上来实现异步,但这是错误的.您可以在烤面包机上烤制早餐和阅读纸张,而无需雇佣厨师观看烤面包机.人们说,好吧,必须有一个线 - 一个工人 - 隐藏在烤面包机内,但我向你保证,如果你看着你的烤面包机,那里没有小家伙在看烤面包. (60认同)
  • @StrugglingCoder:那么,你在做什么工作?也许另一个线程正在进行工作,并且该线程已经分配给CPU,因此工作实际上已经完成.也许这项工作是由硬件完成的,根本就没有线程.但肯定的是,你说,硬件必须有**线程**.否.硬件存在于线程级别以下.不需要线程!您可能会从阅读Stephen Cleary的文章"没有线程"中受益. (11认同)
  • @StrugglingCoder:现在,想想我刚刚说的话.**您已经知道这是Windows的工作原理**.您执行一系列鼠标移动和按钮单击等等.消息排队,依次处理,每条消息都会导致少量工作完成,一旦完成,系统就会继续运行.在一个线程上的异步只不过是你已经习惯的:将大任务分解成小位,将它们排队,并按某种顺序执行所有的小位.其中一些执行导致*其他*工作排队等待,生活还在继续.一个线程! (8认同)
  • @StrugglingCoder:现在,问一下,假设正在进行异步工作并且没有硬件,并且没有其他线程.这怎么可能?那么,假设你等待的东西**排队了一系列的Windows消息**,每个消息都做了一些工作?现在发生了什么?您将控制权返回给消息循环,它开始将消息拉出队列,每次都做一些工作,最后完成的工作是"执行任务的继续".没有额外的线程! (6认同)
  • @ronag不,不是.如果等待一个需要10毫秒的"任务"实际上会在你的线程上执行一个长达10个小时的"任务",你会如何阻止整整10个小时? (5认同)
  • @NoelWidmer:如果您要表达的是"我不关心此任务何时完成;继续运行此工作流程",那么*根本就不要等待*.await的含义是**这是在此任务完成之前此工作流无法继续的位置**.如果您认为等待意味着"使这个异步",那么您需要立即停止考虑*.等待意味着"如果这个已经异步的工作没有完成,就找其他的事情做". (3认同)
  • @NoelWidmer:不要考虑*加速任务*.把它想象成*让工人既忙又响应*.任务运行速度与运行速度一样快; 异步是关于有效利用工人.如果你有一份可以轻松分工的工作,你只想雇用更多的工人.如果烤一个馅饼需要20分钟,雇用20名厨师就不会在一分钟内找到馅饼. (2认同)
  • @AmitJoshi:async-await肯定可以通过两种方式帮助进行CPU繁重的操作。首先,它可以帮助您进行描述。使用TPL将工作拆分为与CPU一样多的部分,并行运行工作,然后等待结果。如果工作是高度可并行化的,并且比分配线程更昂贵,那么这就是胜利。其次,您可以将所有工作保留在UI线程上,但每10毫秒左右放入一次任务。您的工作将花费更长的时间,但是线程将响应用户输入,而不是挂起UI。 (2认同)
  • @AmitJoshi:要记住的重要一点是,异步等待是关于*协调包含高延迟操作的工作流程*。延迟的来源是 IO 还是 CPU 并不重要;这是任务提供者需要担心的。 (2认同)

Jon*_*Jon 118

为了演示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);
}
Run Code Online (Sandbox Code Playgroud)

  • 代码为+1(运行一次比阅读百次更好).但是短语"//如果你按下Button2,现在你不会在控制台中看到任何东西,直到这个任务完成,然后标签将被更新!`"具有误导性.在按钮点击事件处理程序`ButtonClick()`按下按钮时,按钮点击事件处理程序`ButtonClick()`无法按任何内容然后在控制台中查看内容并更新标签"直到此任务完成",因为GUI冻结和无响应,即任务点击或与GUI的交互正在**丢失**直到完成任务等待 (26认同)
  • 我想Eric假设您对Task api有基本的了解.我查看那段代码,然后对自己说"呀`t.Wait`将在主线程上阻塞,直到任务完成." (2认同)

Mas*_*Mas 48

这个例子非常清楚地展示了这种差异 使用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);
}
Run Code Online (Sandbox Code Playgroud)

DoAsTask输出:

[1] Program Begin
[1] 1 - Starting
[1] 2 - Task started
[3] A - Started something
[3] B - Completed something
[1] 3 - Task completed with result: 123
[1] Program End

DoAsAsync输出:

[1] Program Begin
[1] 1 - Starting
[1] 2 - Task started
[3] A - Started something
[1] Program End
[3] B - Completed something
[3] 3 - Task completed with result: 123

更新:通过在输出中显示线程ID来改进示例.

  • 但是,如果我这样做:新任务(DoAsTask).Start(); 而不是DoAsAsync(); 我得到相同的功能,所以等待的好处在哪里.. (4认同)

Teo*_*ahi 10

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

例如,您有一个asp.net Web应用程序.UserA调用/ getUser/1端点.asp.net app pool将从线程池(Thread1)中选择一个线程,并且该线程将进行http调用.如果你执行Wait(),这个线程将被阻塞,直到http调用解析.在等待时,如果UserB调用/ getUser/2,那么app pool将需要服务另一个线程(Thread2)再次进行http调用.你刚刚创建了(好吧,实际从app池中获取)另一个线程,因为你无法使用Thread1它被Wait()阻止了.

如果在Thread1上使用await,则SyncContext将管理Thread1和http调用之间的同步.简单地说,一旦http呼叫完成,它将通知.同时,如果UserB调用/ getUser/2,那么,你将再次使用Thread1进行http调用,因为它一旦被等待就被释放了.然后另一个请求可以使用它,甚至更多.一旦http调用完成(user1或user2),Thread1就可以获得结果并返回调用者(客户端).Thread1用于多个任务.


fos*_*son 9

在这个例子中,实际上并不多.如果您正在等待在不同线程上返回的任务(如WCF调用)或放弃对操作系统的控制(如文件IO),则await将通过不阻塞线程来使用较少的系统资源.