ChatGPT解决这个技术问题 Extra ChatGPT

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

我可能遗漏了一些东西,但是做这件事有什么区别:

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

E
Eric Lippert

我可能遗漏了一些东西

你是。

Task.Wait 和 await 任务有什么区别?

您从餐厅的服务员那里订购午餐。在您下订单后不久,一位朋友走进来并坐在您旁边并开始交谈。现在你有两个选择。你可以忽略你的朋友,直到任务完成——你可以等到汤上来,在等待的时候什么也不做。或者你可以回应你的朋友,当你的朋友停止说话时,服务员会给你端上你的汤。

Task.Wait 阻塞直到任务完成 - 在任务完成之前您忽略您的朋友。 await 继续处理消息队列中的消息,当任务完成时,它会将一条消息排入队列,上面写着“在等待之后从你离开的地方继续”。你和你的朋友交谈,当谈话中断时,汤就来了。


@ronag 不,不是。如果等待 10 毫秒的 Task 实际上会在您的线程上执行一个 10 小时长的 Task,从而阻塞您整个 10 小时,您会怎么想?
@StrugglingCoder: await 运算符除了评估其操作数然后立即将任务返回给当前调用者之外什么都不做。人们在脑海中产生这样的想法,即异步只能通过将工作卸载到线程上来实现,但这是错误的。您可以在烤面包机中煮早餐并阅读报纸,而无需聘请厨师看烤面包机。人们说好吧,烤面包机里面肯定藏着一根线——一个工人,但我向你保证,如果你看看你的烤面包机,里面没有小家伙在看烤面包。
@StrugglingCoder:那么,谁在做你问的工作?也许另一个线程正在做这项工作,并且该线程已被分配给一个 CPU,所以工作实际上正在完成。也许工作是由硬件完成的,根本没有线程。但是,您肯定会说,硬件中一定有一些线程。不,硬件存在于线程级别之下。不需要有线程!您可能会从 Stephen Cleary 的文章 There Is No Thread 中受益。
@StrugglingCoder:现在,问题,假设正在完成异步工作并且没有硬件,也没有其他线程。这怎么可能?好吧,假设您等待的东西将一系列 Windows 消息排成队列,每个消息都做一些工作?现在会发生什么?您将控制权返回给消息循环,它开始将消息从队列中拉出,每次都做一些工作,最后完成的工作是“执行任务的延续”。没有多余的线程!
@StrugglingCoder:现在,想想我刚才说的话。您已经知道这就是 Windows 的工作方式。您执行一系列鼠标移动和按钮单击等。消息排队,依次处理,每条消息都会导致完成少量工作,当所有工作完成后,系统会继续运行。一个线程上的异步只不过是您已经习惯的:将大任务分解成小块,将它们排队,并以某种顺序执行所有小块。其中一些处决导致其他工作排队,生活还在继续。一根线!
G
Gennady Vanin Геннадий Ванин

为了演示埃里克的答案,这里有一些代码:

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 将阻塞主线程,直到任务完成。”
M
Mas

这个例子非常清楚地说明了差异。使用 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] 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 来改进示例。


但如果我这样做:new Task(DoAsTask).Start();而不是 DoAsAsync();我得到了相同的功能,所以等待的好处在哪里..
根据您的建议,任务的结果必须在其他地方进行评估,可能是另一种方法或 lambda。 async-await 使异步代码更易于遵循。它只是一个语法增强器。
@Mas 我不明白为什么 Program End 在 A - Started something 之后。根据我的理解,等待关键字过程应该立即进入主要上下文,然后返回。
@JimmyJimm 据我了解,Task.Factory.StartNew 将启动一个新线程来运行 DoSomethingThatTakesTime。因此,不能保证 Program End 或 A - Started Something 会先执行。
@JimmyJimm:我更新了示例以显示线程 ID。如您所见,“程序结束”和“A - Started something”在不同的线程上运行。所以实际上顺序不是确定性的。
T
Teoman shipahi

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 用于多个任务。


D
David Klempfner

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


u
user8545699

在上面的例子中,可以使用“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;
}