ความแตกต่างระหว่าง 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);
}

คำตอบ:


395

ฉันอาจจะหายไปบางสิ่งบางอย่าง

คุณคือ.

อะไรคือความแตกต่างระหว่างการทำTask.Waitและawait task?

คุณสั่งอาหารกลางวันจากบริกรที่ร้านอาหาร ครู่หนึ่งหลังจากที่คุณส่งคำสั่งซื้อเพื่อนคนหนึ่งเดินเข้ามาและนั่งถัดจากคุณและเริ่มการสนทนา ตอนนี้คุณมีสองทางเลือก คุณสามารถเพิกเฉยเพื่อนของคุณจนกว่างานจะเสร็จสมบูรณ์ - คุณสามารถรอจนกว่าซุปของคุณมาถึงและไม่ทำอะไรเลยในขณะที่คุณรอ หรือคุณสามารถตอบสนองต่อเพื่อนของคุณและเมื่อเพื่อนของคุณหยุดพูดพนักงานเสิร์ฟจะนำซุปมาให้คุณ

Task.Waitบล็อกจนกว่างานจะเสร็จสมบูรณ์ - คุณไม่สนใจเพื่อนของคุณจนกว่างานจะเสร็จสมบูรณ์ awaitประมวลผลข้อความในคิวข้อความและเมื่องานเสร็จสมบูรณ์จะจัดคิวข้อความที่ระบุว่า "เลือกตำแหน่งที่คุณค้างไว้หลังจากนั้นรอ" คุณพูดคุยกับเพื่อนของคุณและเมื่อมีการแบ่งในการสนทนาซุปมาถึง


5
@ronag ไม่มันไม่ใช่ คุณจะชอบมันอย่างไรถ้าการรอTaskที่ใช้เวลา 10 ms จริง ๆ แล้วจะรันTaskบนเธรดของคุณนาน 10 ชั่วโมงซึ่งจะบล็อกคุณตลอด 10 ชั่วโมง
svick

62
@StrugglingCoder: ผู้ดำเนินการที่รอคอยไม่ได้ทำอะไรเลยยกเว้นประเมินตัวถูกดำเนินการแล้วส่งคืนงานให้กับผู้เรียกปัจจุบันทันที ผู้คนได้รับแนวคิดนี้ในหัวของพวกเขาว่าการซิงโครนัสสามารถทำได้โดยการถ่ายข้อมูลลงในเธรดเท่านั้น คุณสามารถปรุงอาหารเช้าและอ่านกระดาษในขณะที่ขนมปังปิ้งอยู่ในเครื่องปิ้งขนมปังโดยไม่ต้องจ้างพ่อครัวเพื่อดูเครื่องปิ้งขนมปัง ผู้คนบอกว่าอย่างดีต้องมีด้าย - คนงาน - ซ่อนอยู่ในเครื่องปิ้งขนมปัง แต่ฉันรับรองกับคุณว่าถ้าคุณมองเข้าไปในเครื่องปิ้งขนมปังของคุณ
Eric Lippert

11
@StugglingCoder: ดังนั้นใครที่ทำงานที่คุณถาม อาจมีเธรดอื่นกำลังทำงานและเธรดนั้นถูกกำหนดให้กับ CPU ดังนั้นงานจึงเสร็จสิ้น บางทีงานกำลังดำเนินการโดยฮาร์ดแวร์และไม่มีเธรดเลย แต่แน่นอนคุณจะพูดว่าจะต้องมีด้ายบางในฮาร์ดแวร์ ไม่ฮาร์ดแวร์มีระดับต่ำกว่าระดับของเธรด ไม่จำเป็นต้องมีหัวข้อ! คุณอาจได้รับประโยชน์จากการอ่านบทความของ Stephen Cleary ไม่มีหัวข้อ
Eric Lippert

6
@StugglingCoder: ตอนนี้คำถามสมมติว่ามีการทำงานแบบอะซิงโครนัสและไม่มีฮาร์ดแวร์และไม่มีเธรดอื่น เป็นไปได้อย่างไร? สมมติว่าสิ่งที่คุณรอคิวชุดของข้อความ windowsแต่ละอันทำงานเล็กน้อย จะเกิดอะไรขึ้น คุณกลับมาควบคุมการวนรอบข้อความมันเริ่มดึงข้อความออกจากคิวทำงานเล็กน้อยทุกครั้งและงานสุดท้ายที่ทำคือ "ดำเนินการต่อเนื่องของงาน" ไม่มีเธรดพิเศษ!
Eric Lippert

8
@StugglingCoder: ตอนนี้คิดเกี่ยวกับสิ่งที่ฉันเพิ่งพูด คุณรู้อยู่แล้วว่านี่เป็นวิธีการทำงานของ Windows คุณดำเนินการชุดของการเคลื่อนไหวของเมาส์และคลิกปุ่มและอะไร ข้อความถูกจัดคิวประมวลผลตามลำดับแต่ละข้อความทำให้เกิดงานจำนวนเล็กน้อยและเมื่อเสร็จแล้วระบบจะทำงานต่อไป Async ในหนึ่งเธรดไม่มีอะไรมากไปกว่าสิ่งที่คุณคุ้นเคยอยู่แล้ว: การแบ่งงานใหญ่ ๆ ออกเป็นชิ้นเล็ก ๆ น้อย ๆ รอคิวพวกเขาและเรียกใช้บิตเล็ก ๆ น้อย ๆ ตามลำดับ การประหารชีวิตบางส่วนทำให้งานอื่นต้องเข้าคิวและชีวิตดำเนินต่อไป หนึ่งกระทู้!
Eric Lippert

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();คลิกปุ่มตัวจัดการเหตุการณ์ButtonClick()มันเป็นไปไม่ได้ที่จะกดอะไรแล้วเห็นบางอย่างในคอนโซลและอัปเดตฉลาก "จนกว่างานนี้จะเสร็จสมบูรณ์" เนื่องจาก GUI ค้างและไม่ตอบสนองนั่นคือการคลิกหรือการโต้ตอบกับ GUI กำลังสูญเสีย จนกว่างานจะเสร็จสิ้นการรอคอย
Gennady Vanin ГеннадийВанин

2
ฉันเดาว่า Eric ถือว่าคุณมีความเข้าใจพื้นฐานของ Task api ฉันดูรหัสนั้นและพูดกับตัวเองว่า "yup t.Waitกำลังจะบล็อกบนเธรดหลักจนกว่างานจะเสร็จสมบูรณ์"
ชายมัฟฟิ

50

ตัวอย่างนี้แสดงให้เห็นถึงความแตกต่างอย่างชัดเจน ด้วย async / รอเธรดการโทรจะไม่บล็อกและดำเนินการต่อ

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
แต่ถ้าฉันทำ: งานใหม่ (DoAsTask) .Start (); แทน DoAsAsync (); ฉันได้ฟังก์ชั่นการทำงานเหมือนกันดังนั้นประโยชน์ของการรอคอยอยู่
ที่ไหน

1
ด้วยข้อเสนอของคุณผลลัพธ์ของงานจะต้องได้รับการประเมินที่อื่นอาจเป็นวิธีอื่นหรือแลมบ์ดา async-await ทำให้โค้ดแบบอะซิงโครนัสง่ายต่อการติดตาม มันเป็นเพียงตัวเสริมซินแทกซ์
Mas

@ ฉันไม่เข้าใจว่าเพราะเหตุใดโปรแกรมสิ้นสุดลงหลังจาก A - เริ่มบางสิ่งบางอย่าง จากความเข้าใจของฉันเมื่อมันมาถึงกระบวนการคำหลักที่ควรจะไปทันทีที่บริบทหลักแล้วกลับไป

@ JimmyJimm จากความเข้าใจของฉัน Task.Fartory.StartNew จะหมุนเธรดใหม่เพื่อรัน DoSomethingThatTakesTime ดังนั้นจึงไม่มีการรับประกันว่าจุดสิ้นสุดของโปรแกรมหรือ A - เริ่มต้นบางสิ่งจะถูกดำเนินการก่อน
RiaanDP

@JimmyJimm: ฉันได้อัปเดตตัวอย่างเพื่อแสดงรหัสเธรด อย่างที่คุณเห็น "การสิ้นสุดโปรแกรม" และ "A - สิ่งที่เริ่มต้น" กำลังทำงานบนเธรดที่แตกต่างกัน ดังนั้นจริงๆแล้วคำสั่งไม่ได้กำหนดไว้
Mas

10

รอ () จะทำให้รหัส async อาจเกิดขึ้นในลักษณะการซิงค์ รอจะไม่

ตัวอย่างเช่นคุณมีแอปพลิเคชันเว็บ asp.net UserA เรียก / getUser / 1 ปลายทาง แอพ asp.net จะเลือกเธรดจากเธรดพูล (เธรด 1) และเธรดนี้จะทำการเรียก http หากคุณรอ () เธรดนี้จะถูกบล็อกจนกว่าการโทร http จะหายไป ในขณะที่กำลังรอถ้า UserB เรียก / getUser / 2 จากนั้นแอพพลิเคชั่นจะต้องให้บริการเธรดอื่น (Thread2) เพื่อทำการโทร http อีกครั้ง คุณเพิ่งสร้าง (เอามาจากกลุ่มแอพจริงๆ) เธรดอื่นโดยไม่มีเหตุผลเพราะคุณไม่สามารถใช้เธรด 1 ที่ถูกบล็อกโดย Wait ()

หากคุณใช้รอบน Thread1 ดังนั้น SyncContext จะจัดการการซิงค์ระหว่างการโทร Thread1 และ http เพียงแค่มันจะแจ้งเตือนเมื่อมีการเรียก http ในขณะเดียวกันถ้า UserB เรียก / getUser / 2 คุณจะใช้ Thread1 อีกครั้งเพื่อทำการโทร http เนื่องจากจะถูกปล่อยเมื่อรอการกด จากนั้นคำขออื่นสามารถใช้งานได้มากยิ่งขึ้น เมื่อการโทร http เสร็จสิ้นแล้ว (user1 หรือ user2) Thread1 จะได้รับผลลัพธ์และกลับไปที่ผู้โทร (ไคลเอ็นต์) Thread1 ถูกใช้สำหรับหลาย ๆ งาน


9

ในตัวอย่างนี้ไม่มากนัก หากคุณกำลังรองานที่ส่งคืนเธรดอื่น (เช่นการเรียก WCF) หรือยกเลิกการควบคุมระบบปฏิบัติการ (เช่น File IO) รอจะใช้ทรัพยากรระบบน้อยลงโดยไม่บล็อกเธรด


3

ในตัวอย่างข้างต้นคุณสามารถใช้ "TaskCreationOptions.HideScheduler" และปรับเปลี่ยนวิธีการ "DoAsTask" อย่างมาก วิธีการนี้ไม่ได้เป็นแบบอะซิงโครนัสเนื่องจากมันเกิดขึ้นกับ "DoAsAsync" เพราะมันจะส่งกลับค่า "งาน" และถูกทำเครื่องหมายเป็น "async" ทำให้การรวมกันหลายครั้งนี่คือวิธีที่มันทำให้ฉันเหมือนกับ :

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;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.