Sự khác biệt giữa Task.Start / Wait và Async / Await là gì?


206

Tôi có thể đang thiếu một cái gì đó nhưng sự khác biệt giữa việc làm:

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

Câu trả lời:


395

Tôi có thể đang thiếu một cái gì đó

Bạn là.

sự khác biệt giữa làm Task.Waitvà là await taskgì?

Bạn gọi bữa trưa từ người phục vụ tại nhà hàng. Một lúc sau khi ra lệnh, một người bạn bước vào và ngồi xuống cạnh bạn và bắt đầu một cuộc trò chuyện. Bây giờ bạn có hai lựa chọn. Bạn có thể bỏ qua người bạn của mình cho đến khi nhiệm vụ hoàn thành - bạn có thể đợi cho đến khi món súp của bạn đến và không làm gì khác trong khi bạn đang chờ đợi. Hoặc bạn có thể trả lời bạn của bạn, và khi bạn của bạn ngừng nói chuyện, người phục vụ sẽ mang cho bạn món súp của bạn.

Task.Waitchặn cho đến khi nhiệm vụ hoàn thành - bạn bỏ qua người bạn của mình cho đến khi nhiệm vụ hoàn thành. awaittiếp tục xử lý tin nhắn trong hàng đợi tin nhắn và khi tác vụ hoàn thành, nó sẽ hiển thị một thông báo có nội dung "chọn nơi bạn rời đi sau khi chờ đợi". Bạn nói chuyện với bạn của bạn, và khi có một cuộc chia tay trong cuộc trò chuyện, súp sẽ đến.


5
@ronag Không, không phải vậy. Bạn muốn nó như thế nào nếu chờ đợi Taskmất 10 ms thực sự sẽ thực hiện 10 giờ Tasktrên chuỗi của bạn, do đó chặn bạn trong toàn bộ 10 giờ?
Svick

62
@StrugglingCoder: Toán tử await không làm gì ngoại trừ đánh giá toán hạng của nó và sau đó trả lại ngay một tác vụ cho người gọi hiện tại . Mọi người nghĩ ra ý tưởng này rằng sự không đồng bộ chỉ có thể đạt được thông qua việc giảm tải công việc lên các luồng, nhưng điều đó là sai. Bạn có thể nấu bữa sáng và đọc giấy trong khi bánh mì nướng ở trong lò nướng bánh mà không cần thuê đầu bếp để xem máy nướng bánh mì. Mọi người nói rằng, phải có một sợi chỉ - một công nhân - ẩn bên trong lò nướng bánh, nhưng tôi đảm bảo với bạn rằng nếu bạn nhìn vào lò nướng bánh của mình, sẽ không có chàng trai nào ở đó xem bánh mì nướng.
Eric Lippert

11
@StrugglingCoder: Vậy, ai đang làm công việc bạn yêu cầu? Có thể một luồng khác đang thực hiện công việc và luồng đó đã được gán cho CPU, vì vậy công việc thực sự đang được thực hiện. Có lẽ công việc đang được thực hiện bởi phần cứng và không có chủ đề nào cả. Nhưng chắc chắn, bạn nói, phải có một số chủ đề trong phần cứng . Không. Phần cứng tồn tại dưới mức của các chủ đề. Không cần có chủ đề! Bạn có thể được hưởng lợi từ việc đọc bài viết của Stephen Cleary Không có chủ đề.
Eric Lippert

6
@StrugglingCoder: Bây giờ, câu hỏi, giả sử có công việc không đồng bộ đang được thực hiện và không có phần cứng, và không có chủ đề khác. Sao có thể như thế được? Chà, giả sử điều bạn chờ đợi xếp hàng loạt tin nhắn windows , mỗi tin nhắn sẽ thực hiện một chút công việc? Bây giờ chuyện gì xảy ra? Bạn trả lại quyền điều khiển cho vòng lặp thông báo, nó bắt đầu kéo các tin nhắn ra khỏi hàng đợi, thực hiện một chút công việc mỗi lần và công việc cuối cùng được thực hiện là "thực hiện tiếp tục nhiệm vụ". Không có chủ đề thêm!
Eric Lippert

8
@StrugglingCoder: Bây giờ, hãy nghĩ về những gì tôi vừa nói. Bạn đã biết rằng đây là cách Windows hoạt động . Bạn thực hiện một loạt các chuyển động chuột và nhấp chuột và không có gì. Các tin nhắn được xếp hàng, xử lý lần lượt, mỗi tin nhắn sẽ khiến một lượng công việc nhỏ được thực hiện và khi hoàn thành, hệ thống sẽ tiếp tục. Không đồng bộ trên một luồng không có gì khác hơn những gì bạn đã sử dụng: chia nhỏ các tác vụ lớn thành các bit nhỏ, xếp hàng chúng và thực hiện tất cả các bit nhỏ theo thứ tự. Một số trong những vụ hành quyết đó khiến các công việc khác bị xếp hàng, và cuộc sống vẫn tiếp diễn. Một chủ đề!
Eric Lippert

121

Để chứng minh câu trả lời của Eric ở đây là một số mã:

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 cho mã (tốt hơn là chạy một lần hơn là đọc hàng trăm lần). Nhưng cụm từ " //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!" là sai lệch. Khi nhấn nút bằng t.Wait();trình xử lý sự kiện nhấp vào nút, ButtonClick()không thể nhấn bất cứ thứ gì và sau đó thấy một cái gì đó trong bảng điều khiển và cập nhật nhãn "cho đến khi tác vụ này hoàn tất" do GUI bị đóng băng và không phản hồi, đó là bất kỳ nhấp chuột hoặc tương tác nào với GUI đang bị MẤT cho đến khi hoàn thành nhiệm vụ chờ đợi
Gennady Vanin Геннадий В Cửa hàng

2
Tôi đoán Eric giả định rằng bạn có hiểu biết cơ bản về api Nhiệm vụ. Tôi nhìn vào mã đó và tự nói với mình "yup t.Waitsẽ chặn trên luồng chính cho đến khi nhiệm vụ hoàn thành."
Người đàn ông Muffin

50

Ví dụ này cho thấy sự khác biệt rất rõ ràng. Với async / await, chuỗi gọi sẽ không chặn và tiếp tục thực thi.

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

Đầu ra DoAsTask:

[1] Chương trình bắt đầu
[1] 1 - Bắt đầu
[1] 2 - Nhiệm vụ bắt đầu
[3] A - Bắt đầu một cái gì đó
[3] B - Đã hoàn thành một cái gì đó
[1] 3 - Nhiệm vụ hoàn thành với kết quả: 123
[1] Kết thúc chương trình

Đầu ra DoAsAsync:

[1] Chương trình bắt đầu
[1] 1 - Bắt đầu
[1] 2 - Nhiệm vụ bắt đầu
[3] A - Bắt đầu một cái gì đó
[1] Kết thúc chương trình
[3] B - Đã hoàn thành một cái gì đó
[3] 3 - Nhiệm vụ hoàn thành với kết quả: 123

Cập nhật: Ví dụ được cải thiện bằng cách hiển thị ID luồng trong đầu ra.


4
Nhưng nếu tôi làm: Nhiệm vụ mới (DoAsTask) .Start (); thay vì DoAsAsync (); tôi có được chức năng tương tự, vì vậy lợi ích của việc chờ đợi ở đâu ..
omriman12

1
Với đề xuất của bạn, kết quả của nhiệm vụ phải được đánh giá ở một nơi khác, có thể là phương pháp khác hoặc lambda. Async-await là làm cho mã không đồng bộ dễ theo dõi hơn. Nó chỉ là một cải tiến cú pháp.
Mas

@ Tôi không hiểu tại sao Chương trình kết thúc sau A - Bắt đầu một cái gì đó. Theo hiểu biết của tôi khi chờ đợi quá trình từ khóa sẽ đi ngay đến bối cảnh chính và sau đó quay trở lại.

@JimmyJimm Từ sự hiểu biết của tôi Nhiệm vụ.Factory.StartNew sẽ tạo ra một chủ đề mới để chạy DoS SomethingThatTakesTime. Vì vậy, không có gì đảm bảo liệu Chương trình kết thúc hay A - Bắt đầu Một cái gì đó sẽ được thực hiện trước tiên.
RiaanDP

@JimmyJimm: Tôi đã cập nhật mẫu để hiển thị ID luồng. Như bạn có thể thấy, "Kết thúc chương trình" và "A - Bắt đầu một cái gì đó" đang chạy trên các luồng khác nhau. Vì vậy, thực sự thứ tự là không xác định.
Mas

10

Chờ (), sẽ khiến chạy mã async có khả năng theo cách đồng bộ hóa. chờ đợi sẽ không.

Ví dụ: bạn có một ứng dụng web asp.net. Các cuộc gọi UserA / getUser / 1 điểm cuối. Nhóm ứng dụng asp.net sẽ chọn một luồng từ nhóm luồng (Thread1) và, luồng này sẽ thực hiện cuộc gọi http. Nếu bạn thực hiện Wait (), chuỗi này sẽ bị chặn cho đến khi cuộc gọi http được giải quyết. Trong khi chờ, nếu UserB gọi / getUser / 2, thì nhóm ứng dụng sẽ cần cung cấp một luồng khác (Thread2) để thực hiện lại cuộc gọi http. Bạn vừa tạo (Chà, thực sự được tải từ nhóm ứng dụng) một chủ đề khác mà không có lý do, vì bạn không thể sử dụng Thread1, nó đã bị Wait () chặn.

Nếu bạn sử dụng await trên Thread1, thì SyncContext sẽ quản lý đồng bộ hóa giữa cuộc gọi Thread1 và http. Đơn giản, nó sẽ thông báo khi cuộc gọi http được thực hiện. Trong khi đó, nếu UserB gọi / getUser / 2, thì bạn sẽ sử dụng lại Thread1 để thực hiện cuộc gọi http, vì nó đã được phát hành một khi chờ đợi bị tấn công. Sau đó, một yêu cầu khác có thể sử dụng nó, thậm chí nhiều hơn nữa. Khi cuộc gọi http được thực hiện (user1 hoặc user2), Thread1 có thể nhận kết quả và trả về cho người gọi (máy khách). Thread1 đã được sử dụng cho nhiều nhiệm vụ.


9

Trong ví dụ này, không nhiều, thực tế. Nếu bạn đang chờ Tác vụ trả về một luồng khác (như cuộc gọi WCF) hoặc từ bỏ quyền kiểm soát đối với hệ điều hành (như Tệp IO), việc chờ đợi sẽ sử dụng ít tài nguyên hệ thống hơn bằng cách không chặn luồng.


3

Trong ví dụ trên, bạn có thể sử dụng "TaskCreationOptions.HideScheduler" và sửa đổi rất nhiều phương thức "DoAsTask". Bản thân phương thức này không đồng bộ, vì nó xảy ra với "DoAsAsync" vì nó trả về giá trị "Nhiệm vụ" và được đánh dấu là "không đồng bộ", thực hiện một số kết hợp, đây là cách nó mang lại cho tôi chính xác như sử dụng "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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.