วิธีการใช้ 'async' และ 'รอ'


1065

จากหนึ่งในความเข้าใจของฉันสิ่งสำคัญที่asyncและawaitทำคือการทำให้รหัสง่ายต่อการเขียนและอ่าน - แต่ใช้พวกเขาเท่ากับวางไข่หัวข้อพื้นหลังเพื่อดำเนินการตรรกะระยะเวลานาน?

ฉันกำลังลองตัวอย่างพื้นฐานที่สุด ฉันได้เพิ่มความคิดเห็นบางส่วนแบบอินไลน์ คุณช่วยอธิบายให้ฉันฟังได้ไหม

// I don't understand why this method must be marked as `async`.
private async void button1_Click(object sender, EventArgs e)
{
    Task<int> access = DoSomethingAsync();
    // task independent stuff here

    // this line is reached after the 5 seconds sleep from 
    // DoSomethingAsync() method. Shouldn't it be reached immediately? 
    int a = 1; 

    // from my understanding the waiting should be done here.
    int x = await access; 
}

async Task<int> DoSomethingAsync()
{
    // is this executed on a background thread?
    System.Threading.Thread.Sleep(5000);
    return 1;
}

48
นอกจากนี้ในตัวอย่างของคุณสังเกตเห็นว่าคุณได้รับคำเตือนเมื่อคุณรวบรวมรหัสข้างต้น ใส่ใจกับคำเตือน มันกำลังบอกคุณว่ารหัสนี้ไม่สมเหตุสมผล
Eric Lippert

คำตอบ:


759

เมื่อใช้asyncและawaitคอมไพเลอร์สร้างเครื่องรัฐในพื้นหลัง

นี่คือตัวอย่างที่ฉันหวังว่าฉันสามารถอธิบายรายละเอียดระดับสูงบางอย่างที่เกิดขึ้น:

public async Task MyMethodAsync()
{
    Task<int> longRunningTask = LongRunningOperationAsync();
    // independent work which doesn't need the result of LongRunningOperationAsync can be done here

    //and now we call await on the task 
    int result = await longRunningTask;
    //use the result 
    Console.WriteLine(result);
}

public async Task<int> LongRunningOperationAsync() // assume we return an int from this long running operation 
{
    await Task.Delay(1000); // 1 second delay
    return 1;
}

ตกลงดังนั้นจะเกิดอะไรขึ้นที่นี่:

  1. Task<int> longRunningTask = LongRunningOperationAsync(); เริ่มดำเนินการ LongRunningOperation

  2. งานอิสระเสร็จสิ้นแล้วสมมติว่าเป็นเธรดหลัก (ID เธรด = 1) จากนั้นawait longRunningTaskถึง

    ตอนนี้ถ้าlongRunningTaskยังไม่เสร็จและมันยังคงทำงานอยู่MyMethodAsync()จะกลับสู่วิธีการโทรดังนั้นเธรดหลักจะไม่ถูกบล็อก เมื่อlongRunningTaskเสร็จแล้วเธรดจาก ThreadPool (สามารถเธรดใด ๆ ) จะกลับไปMyMethodAsync()ในบริบทก่อนหน้าและดำเนินการต่อ (ในกรณีนี้พิมพ์ผลลัพธ์ไปยังคอนโซล)

กรณีที่สองคือกรณีที่การlongRunningTaskดำเนินการเสร็จสิ้นแล้วและผลลัพธ์พร้อมใช้งาน เมื่อถึงที่await longRunningTaskเรามีผลแล้วรหัสจะดำเนินการต่อบนเธรดเดียวกัน (ในกรณีนี้ผลการพิมพ์ไปยังคอนโซล) แน่นอนว่านี่ไม่ใช่กรณีสำหรับตัวอย่างข้างต้นที่มีTask.Delay(1000)ส่วนเกี่ยวข้อง


65
เหตุใดเราจึงมี "รอ" ด้วย "Task.Delay (1,000)" ใน LongRunningOperation วิธี async?
Benison Sam

3
@codea ในความคิดเห็นของ Eric Lippert กับบทความที่เขาเชื่อมโยงบทความเบื้องต้นกับหัวข้อนี้ซึ่งเขาเปรียบเทียบกลยุทธ์ DoEvents กับ async-await โดยเฉพาะ
Camilo Martinez

13
@ BenisonSam เธรดค่อนข้างเก่า แต่ฉันมีคำถามเดียวกันและกำลังหาคำตอบ เหตุผลที่ "รอ" คือถ้าเราไม่ใช้ "รอ" LongRunningOperationAsync () จะกลับมาทันที ในความเป็นจริงคอมไพเลอร์จะให้คำเตือนถ้าเราลบการรอคอย บล็อกโพสต์ของ Stephen Cleary บล็อกblog.stephencleary.com/2011/09/…เป็นบทสรุปของการอภิปรายการออกแบบ
shelbypereira

70
หากทุกวิธีการ async จำเป็นต้องมีการรอคอยอยู่ภายในและการรอคอยสามารถทำได้เฉพาะวิธีการที่มี async จะหยุดเมื่อใด
Bruno Santos

108
คำตอบนี้ผิดอย่างชัดเจน upvotes จำนวนมากเหล่านี้จะทำให้ผู้ใช้หลายคนเข้าใจผิด เอกสารของ MS บอกอย่างชัดเจนว่าไม่มีการใช้เธรดอื่นเมื่อใช้ async รอ msdn.microsoft.com/en-us/library/mt674882.aspxโปรดแก้ไขคำตอบให้กับใครสักคน ด้วยเหตุนี้ฉันจึงเสียทั้งวัน
กฤษณะ Deepak

171

จากความเข้าใจของฉันสิ่งหนึ่งที่สำคัญที่ async และรอคอยคือการทำให้โค้ดอ่านและอ่านง่ายขึ้น

พวกมันจะทำให้โค้ดแบบอะซิงโครนัสง่ายต่อการเขียนและอ่านใช่

มันเหมือนกับการวางไข่เธรดพื้นหลังเพื่อดำเนินการตรรกะระยะเวลานานหรือไม่

ไม่ใช่เลย.

// ฉันไม่เข้าใจว่าทำไมต้องทำเครื่องหมายวิธีนี้ว่า 'async'

asyncคำหลักที่ช่วยให้awaitคำหลัก ดังนั้นวิธีการใด ๆ ที่ใช้จะต้องทำเครื่องหมายawaitasync

// บรรทัดนี้มาถึงหลังจากหลับเป็นเวลา 5 วินาทีจากวิธี DoSomethingAsync () ไม่ควรติดต่อทันที

ไม่เพราะasyncวิธีการจะไม่ทำงานบนเธรดอื่นตามค่าเริ่มต้น

// มันถูกประมวลผลบนเธรดพื้นหลังหรือไม่?

เลขที่


คุณอาจพบว่าasync/ awaitบทนำของฉันเป็นประโยชน์ อย่างเป็นทางการเอกสาร MSDNยังดีผิดปกติ (โดยเฉพาะTAPส่วน) และasyncทีมงานนำออกที่ดีเยี่ยมคำถามที่พบบ่อย


6
ดังนั้นจึงไม่ทำงานบนเธรดพื้นหลัง แต่จะไม่บล็อก สิ่งนี้เป็นไปได้เนื่องจาก API แบบอะซิงโครนัสซึ่งใช้การโทรกลับแทนที่จะเล่นกลกับเธรด คุณเริ่มต้นการดำเนินการ (I / O, socket, .. ) และกลับไปทำสิ่งต่าง ๆ ของคุณ เมื่อการดำเนินการเสร็จสิ้น OS จะเรียกใช้การเรียกกลับ นี่คือสิ่งที่ Node.js หรือกรอบงาน Python Twisted ทำและพวกเขาก็มีคำอธิบายที่ดีเช่นกัน
Roman Plášil

3
"คำหลัก async เปิดใช้งานคำหลักที่รอดังนั้นวิธีใดก็ตามที่ใช้การรอจะต้องทำเครื่องหมายเป็นซิงค์", - แต่ทำไม? คำตอบนี้ไม่ช่วยให้เข้าใจว่าทำไมต้องทำเครื่องหมายวิธีเป็น async คอมไพเลอร์ไม่สามารถอนุมานได้ว่าวิธีนั้นเป็นแบบอะซิงโครนัสโดยดูจากข้างในเพื่อรอคำค้นหา
Stanislav

9
@Stanislav: ฉันมีรายการบล็อกที่ตอบคำถามนั้น
Stephen Cleary

3
การชี้แจงที่แนะนำ: ไม่เพราะasyncวิธีการจะไม่ทำงานบนเธรดอื่นตามค่าเริ่มต้น ในตัวอย่างของคุณการSleep()โทรภายในDoSomethingAsync()บล็อกเธรดปัจจุบันซึ่งป้องกันการดำเนินการต่อจากภายในbutton1_Click()จนกว่าจะDoSomethingAsync()เสร็จสมบูรณ์ โปรดทราบว่าในขณะที่Thread.Sleep()บล็อกเธรดที่กำลังดำเนินการอยู่Task.Delay() does not.
DavidRR

166

คำอธิบาย

นี่คือตัวอย่างรวดเร็วของasync/ awaitในระดับสูง มีรายละเอียดเพิ่มเติมมากมายที่ควรพิจารณานอกเหนือจากนี้

หมายเหตุ: Task.Delay(1000)จำลองการทำงานเป็นเวลา 1 วินาที ฉันคิดว่าเป็นการดีที่สุดที่จะคิดว่าสิ่งนี้กำลังรอการตอบสนองจากทรัพยากรภายนอก เนื่องจากรหัสของเรากำลังรอการตอบกลับระบบสามารถตั้งค่าภารกิจที่กำลังวิ่งออกไปด้านข้างและกลับมาที่มันเมื่อมันเสร็จสิ้น ในขณะเดียวกันก็สามารถทำงานอื่น ๆ ในหัวข้อนั้น

ในตัวอย่างด้านล่างบล็อกแรกกำลังทำอย่างนั้น มันจะเริ่มงานทั้งหมดทันที ( Task.Delayเส้น) และตั้งพวกเขาออกไปด้านข้าง รหัสจะหยุดในawait aบรรทัดจนกว่าการหน่วงเวลา 1 วินาทีจะเสร็จสิ้นก่อนที่จะไปยังบรรทัดถัดไป ตั้งแต่b, c, dและeทั้งหมดที่ตั้งขึ้นการดำเนินการที่เกือบจะในเวลาเดียวกันกับa(เนื่องจากการขาดการรอคอย) พวกเขาควรจะจบในเวลาเดียวกันในกรณีนี้

ในตัวอย่างด้านล่างบล็อกที่สองกำลังเริ่มภารกิจและรอให้บล็อกเสร็จ (นั่นคือสิ่งawaitใด) ก่อนเริ่มงานที่ตามมา การวนซ้ำแต่ละครั้งใช้เวลา 1 วินาที awaitจะหยุดโปรแกรมและรอให้ผลก่อนดำเนินการต่อ นี่คือความแตกต่างหลักระหว่างบล็อกแรกและบล็อกที่สอง

ตัวอย่าง

Console.WriteLine(DateTime.Now);

// This block takes 1 second to run because all
// 5 tasks are running simultaneously
{
    var a = Task.Delay(1000);
    var b = Task.Delay(1000);
    var c = Task.Delay(1000);
    var d = Task.Delay(1000);
    var e = Task.Delay(1000);

    await a;
    await b;
    await c;
    await d;
    await e;
}

Console.WriteLine(DateTime.Now);

// This block takes 5 seconds to run because each "await"
// pauses the code until the task finishes
{
    await Task.Delay(1000);
    await Task.Delay(1000);
    await Task.Delay(1000);
    await Task.Delay(1000);
    await Task.Delay(1000);
}
Console.WriteLine(DateTime.Now);

เอาท์พุท:

5/24/2017 2:22:50 PM
5/24/2017 2:22:51 PM (First block took 1 second)
5/24/2017 2:22:56 PM (Second block took 5 seconds)

ข้อมูลเพิ่มเติมเกี่ยวกับ SynchronizationContext

หมายเหตุ: นี่คือสิ่งที่ได้รับหมอกเล็กน้อยสำหรับฉันดังนั้นถ้าฉันผิดกับอะไรโปรดแก้ไขฉันแล้วฉันจะอัปเดตคำตอบ เป็นสิ่งสำคัญที่จะต้องมีความเข้าใจพื้นฐานเกี่ยวกับวิธีการทำงาน แต่คุณสามารถทำได้โดยไม่ต้องเป็นผู้เชี่ยวชาญตราบใดที่คุณไม่เคยใช้ConfigureAwait(false)แม้ว่าคุณจะมีโอกาสสูญเสียโอกาสในการปรับให้เหมาะสม

มีแง่มุมหนึ่งของสิ่งนี้ที่ทำให้async/ awaitแนวคิดค่อนข้างยากที่จะเข้าใจ นั่นคือข้อเท็จจริงที่ว่าในตัวอย่างนี้สิ่งนี้เกิดขึ้นในเธรดเดียวกัน (หรืออย่างน้อยสิ่งที่ดูเหมือนจะเป็นเธรดเดียวกันที่เกี่ยวข้องกับเธรดSynchronizationContext) โดยค่าเริ่มต้นawaitจะคืนค่าบริบทการซิงโครไนซ์ของเธรดเดิมที่กำลังทำงานอยู่ ตัวอย่างเช่นใน ASP.NET คุณมีสิ่งHttpContextที่เชื่อมโยงกับเธรดเมื่อมีคำขอมาบริบทนี้มีสิ่งต่าง ๆ ที่เฉพาะเจาะจงกับการร้องขอ Http ดั้งเดิมเช่นวัตถุคำขอต้นฉบับซึ่งมีสิ่งต่าง ๆ เช่นภาษาที่อยู่ IP ส่วนหัว ฯลฯ หากคุณสลับเธรดระหว่างการประมวลผลบางสิ่งคุณอาจจะพยายามดึงข้อมูลออกมาจากวัตถุนี้ในอีกด้านหนึ่งHttpContextซึ่งอาจเป็นหายนะ หากคุณรู้ว่าคุณจะไม่ใช้บริบทกับสิ่งใดคุณสามารถเลือกที่จะ "ไม่สนใจ" นี่เป็นการอนุญาตให้โค้ดของคุณรันบนเธรดแยกต่างหากโดยไม่ต้องนำบริบทมาใช้

คุณจะบรรลุสิ่งนี้ได้อย่างไร โดยค่าเริ่มต้นawait a;รหัสจริง ๆ แล้วทำให้สมมติฐานที่คุณต้องการจับภาพและคืนค่าบริบท:

await a; //Same as the line below
await a.ConfigureAwait(true);

หากคุณต้องการอนุญาตให้โค้ดหลักดำเนินการกับเธรดใหม่โดยไม่มีบริบทดั้งเดิมคุณเพียงแค่ใช้ false แทนที่จะเป็น true เพื่อให้รู้ว่าไม่จำเป็นต้องกู้คืนบริบท

await a.ConfigureAwait(false);

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

สิ่งนี้สับสนหรือไม่ ใช่เลย! คุณคิดออกไหม อาจ! เมื่อคุณเข้าใจแนวคิดแล้วไปยังคำอธิบายของ Stephen Cleary ซึ่งมีแนวโน้มที่จะเน้นไปที่คนที่มีความเข้าใจทางเทคนิคของasync/ awaitแล้ว


ให้บอกว่างานทั้งหมดเหล่านี้ส่งคืน int และถ้าฉันใช้ผลลัพธ์งานแรกในงานที่สอง (หรือการคำนวณบางอย่าง) มันจะผิดหรือเปล่า?
veerendra gupta

3
@veerendragupta ใช่ คุณจะเลือกที่จะไม่เรียกใช้แบบอะซิงโครนัสในกรณีนั้นอย่างมีสติ (เพราะไม่แบบอะซิงโครนัส) นอกจากนี้ยังมีอีกสองสามสิ่งที่ต้องคำนึงถึงเกี่ยวกับบริบทการกำหนดค่าซึ่งฉันจะไม่เข้าไปที่นี่
Joe Phillips

ดังนั้นawait MethodCall()ขยะแน่นอน คุณอาจรวมทั้งวางawait/ async?
Vitani

2
@Jocie ไม่มาก เมื่อคุณโทรawaitมาฉันคิดว่ามันจะปล่อยเธรดกลับไปที่พูลแทนที่จะเก็บไว้ ทำให้สามารถใช้งานได้จากที่อื่นในขณะที่รอการกลับมาของภารกิจ
Joe Phillips

2
@ JoePhillips ฉันคิดว่าสิ่งที่คุณเพิ่งพูดไปคือสาระสำคัญของ async / ที่รอ เธรดการโทรได้รับการปลดปล่อยและสามารถใช้โดยกระบวนการอื่น ๆ บนเครื่อง เมื่อการโทรที่รอเสร็จสมบูรณ์เธรดใหม่จะถูกใช้เพื่อดำเนินการต่อสิ่งที่ผู้โทรเริ่มต้นไว้ ผู้โทรยังคงรออยู่ แต่ข้อดีคือเธรดจะได้รับการปลดปล่อยในขณะเดียวกัน นั่นคือประโยชน์ของการซิงค์ / รอ?
บ็อบฮอร์น

147

นอกเหนือจากคำตอบอื่น ๆ แล้วดูที่การรอคอย (อ้างอิง C #)

และโดยเฉพาะอย่างยิ่งในตัวอย่างรวมมันอธิบายสถานการณ์ของคุณเล็กน้อย

ตัวอย่างแบบฟอร์ม Windows ต่อไปนี้แสดงให้เห็นถึงการใช้งานของการรอคอยในวิธี async, WaitAsynchronouslyAsync เปรียบเทียบพฤติกรรมของวิธีนั้นกับพฤติกรรมของ WaitSynchronously โดยไม่ต้องมีโอเปอเรเตอร์ที่ใช้กับงาน WaitSynchronously จะทำงานแบบซิงโครนัสแม้จะใช้ async โมดิฟายเออร์ในคำจำกัดความและการเรียกไปยัง Thread.Sleep ในเนื้อความของมัน

private async void button1_Click(object sender, EventArgs e)
{
    // Call the method that runs asynchronously.
    string result = await WaitAsynchronouslyAsync();

    // Call the method that runs synchronously.
    //string result = await WaitSynchronously ();

    // Display the result.
    textBox1.Text += result;
}

// The following method runs asynchronously. The UI thread is not
// blocked during the delay. You can move or resize the Form1 window 
// while Task.Delay is running.
public async Task<string> WaitAsynchronouslyAsync()
{
    await Task.Delay(10000);
    return "Finished";
}

// The following method runs synchronously, despite the use of async.
// You cannot move or resize the Form1 window while Thread.Sleep
// is running because the UI thread is blocked.
public async Task<string> WaitSynchronously()
{
    // Add a using directive for System.Threading.
    Thread.Sleep(10000);
    return "Finished";
}

3
ขอบคุณสำหรับคำตอบ. แต่ WaitAsynchronouslyAsync () จะถูกดำเนินการในเธรดแยกกันหรือไม่
Dan Dinu

32
ฉันเชื่อเช่นนั้นจากส่วนนิพจน์ที่รอไม่ได้บล็อกเธรดที่กำลังเรียกใช้งาน แต่จะทำให้คอมไพเลอร์ลงทะเบียนส่วนที่เหลือของวิธีการ async เป็นความต่อเนื่องในงานที่รอ จากนั้นการควบคุมจะกลับไปที่ผู้เรียกวิธีการ async เมื่อภารกิจเสร็จสิ้นภารกิจจะเรียกใช้การต่อเนื่องและการดำเนินการของวิธีการ async จะดำเนินการต่อจากที่ค้างไว้
Adriaan Stander

13
อ้างอิงจากบทความ MSDN นี้ "คำว่า async และรอคำสำคัญไม่ได้ทำให้เกิดการสร้างเธรดเพิ่มเติม .... วิธีการ async ไม่ทำงานบนเธรดของตัวเอง" ความเข้าใจของฉันคือที่รอคำสำคัญกรอบข้ามไปข้างหน้า (กลับไปที่ผู้โทร) เพื่อให้รหัสอิสระที่เป็นไปได้ทั้งหมดที่จะทำงานในขณะที่รอการดำเนินการนานจะเสร็จสิ้น ฉันคิดว่าหมายความว่าเมื่อรหัสอิสระทั้งหมดทำงานหากการดำเนินการที่ยาวนานไม่ได้ส่งคืนจะเป็นการบล็อก ฉันเพิ่งจะเรียนรู้สิ่งนี้ตอนนี้
Vimes

9
@ ผู้แปลนั่นไม่ถูกต้อง มันไม่ได้รันบนเธรดอื่น มันเป็นเพียงกำหนดเวลาความต่อเนื่อง (ส่วนที่เหลือของวิธีการ) ที่จะถูกเรียกเมื่อตัวจับเวลาที่ใช้โดยการTask.Delayยิง
MgSam

1
คำตอบนี้ผิดเพราะการนอนหลับ ดูคำตอบที่ยอมรับพร้อมกับรอ Task.Delay (1,000); ซึ่งมีพฤติกรรมที่ถูกต้อง
Jared Updike

62

แสดงคำอธิบายข้างต้นที่ใช้งานได้ในโปรแกรมคอนโซลอย่างง่าย:

class Program
{
    static void Main(string[] args)
    {
        TestAsyncAwaitMethods();
        Console.WriteLine("Press any key to exit...");
        Console.ReadLine();
    }

    public async static void TestAsyncAwaitMethods()
    {
        await LongRunningMethod();
    }

    public static async Task<int> LongRunningMethod()
    {
        Console.WriteLine("Starting Long Running method...");
        await Task.Delay(5000);
        Console.WriteLine("End Long Running method...");
        return 1;
    }
}

และผลลัพธ์คือ:

Starting Long Running method...
Press any key to exit...
End Long Running method...

ดังนั้น,

  1. TestAsyncAwaitMethodsหลักเริ่มวิธีการทำงานผ่านทางยาว ซึ่งจะส่งกลับทันทีโดยไม่หยุดเธรดปัจจุบันและเราจะเห็นข้อความ 'กดปุ่มใด ๆ เพื่อออก' ทันที
  2. ทั้งหมดนี้ในขณะที่LongRunningMethodกำลังทำงานในพื้นหลัง เมื่อเสร็จสิ้นเธรดอื่นจากเธรดพูลจะรับบริบทนี้และแสดงข้อความสุดท้าย

ดังนั้นไม่มีเธรดถูกบล็อก


"กดปุ่มใด ๆ เพื่อออก ... " จะแสดงในส่วนใดของเอาต์พุต
StudioX

1
และการใช้งานของ (คืน 1) คืออะไร? จำเป็นหรือไม่
StudioX

1
@StudioX ฉันคิดว่ามันจะต้องมีประเภทผลตอบแทนจำนวนเต็ม
Kuba Do

ผมคิดว่าreturn 1ส่วนหนึ่งที่สมควรได้รับบางคำอธิบายเพิ่มเติมที่: awaitคำหลักที่ช่วยให้คุณสามารถกลับชนิดพื้นฐานของTask<T>โดยตรงจึงทำให้ง่ายต่อการปรับตัวเข้ากับรหัสออกของคุณไปที่รอคอย / asyncโลก แต่คุณไม่จำเป็นต้องส่งคืนค่าเนื่องจากเป็นไปได้ที่จะส่งคืนTaskโดยไม่ระบุชนิดที่ส่งคืนซึ่งจะเทียบเท่ากับvoidวิธีการซิงโครนัส โปรดทราบว่า C # อนุญาตasync voidวิธีการต่าง ๆ แต่คุณควรหลีกเลี่ยงการกระทำดังกล่าวเว้นแต่ว่าคุณจะจัดการกับตัวจัดการเหตุการณ์
Christiano Kiss

41

ฉันคิดว่าคุณได้เลือกตัวอย่างที่ไม่ดีด้วย System.Threading.Thread.Sleep

จุดของasyncงานคือให้มันทำงานในพื้นหลังโดยไม่ต้องล็อคเธรดหลักเช่นการทำDownloadFileAsync

System.Threading.Thread.Sleep ไม่ใช่สิ่งที่ "กำลังทำอยู่" มันแค่หลับและดังนั้นถึงบรรทัดถัดไปของคุณหลังจาก 5 วินาที ...

อ่านบทความนี้ฉันคิดว่ามันเป็นคำอธิบายที่ดีasyncและawaitแนวคิด: http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx


3
ทำไม Sleep เป็นตัวอย่างที่ไม่ดี แต่การดาวน์โหลดเป็นตัวอย่างที่ดี มันเหมือนกับ FooBar ชนิดของสิ่งเมื่อฉันเห็น Thread.Sleep ฉันเข้าใจว่ามีงานที่ต้องใช้เวลา ฉันคิดว่าคำถามของเขามีความเกี่ยวข้อง
Abdurrahim

1
@Abdurrahim Thread.Sleepบล็อกเธรด (เธรดไม่สามารถทำสิ่งอื่นนอกจากนั่งว่าง) แต่เมธอด async ไม่ ในกรณีของDownloadFileAsyncเธรดสามารถไปและทำอย่างอื่นจนกว่าการตอบกลับมาจากเซิร์ฟเวอร์ระยะไกล ตัวยึดตำแหน่งที่ดีกว่าสำหรับ "งานบางอย่างที่ต้องใช้เวลา" ในวิธีการTask.Delayแบบอะซิงก์คือเนื่องจากเป็นแบบอะซิงโครนัส
Gabriel Luci

@ GabrielLuci การคัดค้านของฉันไม่เกี่ยวกับ Delay vs Sleep; คำตอบของคุณดูคล้ายกับคำตอบของชาวฟาง หากคุณใส่ความคิดเห็นนี้ไว้ในคำถามที่ไม่มีอะไรที่ฉันสามารถคัดค้านได้ แต่เป็นคำตอบที่ได้กลิ่นมากกว่าคำตอบของชาวฟาง ฉันคิดว่าการใช้ async นั้นยังคงใช้ได้แม้ทุกสายที่เขา / เธอต้องทำจะเป็นการบล็อคการโทร มันจะไม่ยกเลิกวัตถุประสงค์ทั้งหมด ... แม้แต่สิ่งที่เหลือจะเป็นน้ำตาลเชิงประโยคมันนับว่าเป็นกรณีที่ถูกต้อง
Abdurrahim

1
นี่ไม่ใช่คำตอบของฉัน แต่การพูดถึงประเด็นของคุณมันขึ้นอยู่กับวัตถุประสงค์ของวิธีการ หากเขาเพียงต้องการวิธีการโทรเขาประสบความสำเร็จ แต่ในกรณีนี้เขาพยายามทำวิธีที่ทำงานแบบอะซิงโครนัส เขาทำอย่างนั้นโดยใช้asyncคำสำคัญ แต่วิธีการของเขายังคงทำงานแบบซิงโครนัสและคำตอบนี้อธิบายได้อย่างสมบูรณ์แบบว่าเพราะเขาไม่ได้เรียกใช้โค้ดอะซิงโครนัส วิธีการทำเครื่องหมายasyncทำงานพร้อมกันจนกระทั่งคุณยังไม่สมบูรณ์await Taskหากไม่มีawaitวิธีการจะทำงานพร้อมกันและคอมไพเลอร์จะเตือนคุณว่า
Gabriel Luci

23

นี่คือโปรแกรมคอนโซลอย่างรวดเร็วเพื่อให้ชัดเจนกับผู้ที่ติดตาม TaskToDoวิธีเป็นวิธีการระยะยาวของคุณที่คุณต้องการที่จะทำให้ async การทำให้มันทำงาน async ทำได้โดยTestAsyncวิธีการ วิธีการทดสอบลูปเพียงทำงานผ่านTaskToDoงานและเรียกใช้พวกเขา async คุณสามารถเห็นได้ว่าในผลลัพธ์เพราะพวกเขาไม่ได้ดำเนินการตามลำดับเดียวกันจากการรันเพื่อรัน - พวกเขากำลังรายงานไปยังเธรด UI ของคอนโซลเมื่อพวกเขาเสร็จสิ้น Simplistic แต่ฉันคิดว่าตัวอย่างง่าย ๆ นำแกนกลางของรูปแบบออกมาดีกว่าตัวอย่างที่เกี่ยวข้อง:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TestingAsync
{
    class Program
    {
        static void Main(string[] args)
        {
            TestLoops();
            Console.Read();
        }

        private static async void TestLoops()
        {
            for (int i = 0; i < 100; i++)
            {
                await TestAsync(i);
            }
        }

        private static Task TestAsync(int i)
        {
            return Task.Run(() => TaskToDo(i));
        }

        private async static void TaskToDo(int i)
        {
            await Task.Delay(10);
            Console.WriteLine(i);
        }
    }
}

20

เพื่อการเรียนรู้ที่เร็วที่สุด ..

  • ทำความเข้าใจกับโฟลว์การประมวลผลเมธอด (พร้อมไดอะแกรม): 3 นาที

  • วิปัสสนาคำถาม (สาเกการเรียนรู้): 1 นาที

  • ได้อย่างรวดเร็วผ่านไวยากรณ์น้ำตาล: 5 นาที

  • แบ่งปันความสับสนของผู้พัฒนา: 5 นาที

  • ปัญหา: เปลี่ยนการใช้งานรหัสโลกปกติเป็นรหัส Async ได้อย่างรวดเร็ว: 2 นาที

  • ไปที่ไหนต่อไป?

ทำความเข้าใจกับโฟลว์การประมวลผลเมธอด (พร้อมไดอะแกรม): 3 นาที

ในภาพนี้เพียงแค่มุ่งเน้นที่ # 6 (ไม่มีอะไรเพิ่มเติม) ป้อนคำอธิบายรูปภาพที่นี่

ที่ # 6 ขั้นตอน: การดำเนินการหยุดลงที่นี่เนื่องจากการทำงานไม่เพียงพอ ในการดำเนินการต่อต้องได้รับผลลัพธ์จาก getStringTask (ชนิดของฟังก์ชัน) ดังนั้นจึงใช้awaitโอเปอเรเตอร์เพื่อระงับความคืบหน้าและให้การควบคุม (ผลตอบแทน) แก่ผู้โทร (ของวิธีการที่เราอยู่) การเรียก getStringTask ที่แท้จริงเกิดขึ้นก่อนหน้านี้ใน # 2 ที่ # 2 สัญญาถูกสร้างขึ้นเพื่อส่งคืนผลลัพธ์สตริง แต่มันจะส่งคืนผลลัพธ์เมื่อใด เราควร (# 1: AccessTheWebAsync) โทรครั้งที่ 2 อีกครั้งหรือไม่ ใครได้รับผลลัพธ์ # 2 (คำสั่งการเรียก) หรือ # 6 (กำลังรอคำสั่ง)

ผู้เรียกภายนอกของ AccessTheWebAsync () ก็กำลังรออยู่เช่นกัน ดังนั้นผู้โทรกำลังรอ AccessTheWebAsync และ AccessTheWebAsync กำลังรอให้ GetStringAsync อยู่ในขณะนี้ สิ่งที่น่าสนใจคือ AccessTheWebAsync ทำงานบางอย่างก่อนรอ (# 4) บางทีเพื่อประหยัดเวลาจากการรอ อิสระในการใช้มัลติทาสก์เดียวกันนี้ยังมีให้สำหรับผู้โทรภายนอก (และผู้โทรทุกคนในเครือข่าย) และนี่เป็นข้อดีที่สุดของ 'async' thingy! คุณรู้สึกเหมือนซิงโครนัส .. หรือปกติ แต่ไม่ใช่

โปรดจำไว้ว่าวิธีการส่งคืนแล้ว (# 2) ไม่สามารถส่งคืนได้อีก (ไม่มีครั้งที่สอง) แล้วผู้โทรจะรู้ได้อย่างไร มันเป็นเรื่องของTasks! ภารกิจผ่านไปแล้ว งานกำลังรอ (ไม่ใช่วิธีไม่ใช่ค่า) ค่าจะถูกตั้งค่าในงาน สถานะงานจะถูกตั้งค่าให้เสร็จสมบูรณ์ ผู้โทรเพียงแค่ตรวจสอบงาน (# 6) ดังนั้น 6 # คือคำตอบของผู้ที่ได้ผลลัพธ์ เพิ่มเติมอ่านในภายหลังที่นี่

คำถามวิปัสสนาเพื่อประโยชน์ในการเรียนรู้: 1 นาที

ให้เราปรับคำถามเล็กน้อย:

อย่างไรและเมื่อไหร่ที่จะใช้และ ? asyncawait Tasks

เพราะการเรียนรู้Taskจะครอบคลุมอีกสองส่วนโดยอัตโนมัติ (และตอบคำถามของคุณ)

ได้อย่างรวดเร็วผ่านไวยากรณ์น้ำตาล: 5 นาที

  • ก่อนการแปลง (วิธีดั้งเดิม)

    internal static int Method(int arg0, int arg1) { int result = arg0 + arg1; IO(); // Do some long running IO. return result; }

  • วิธีการ Task-ified เพื่อเรียกวิธีการดังกล่าว

    internal static Task<int> MethodTask(int arg0, int arg1) { Task<int> task = new Task<int>(() => Method(arg0, arg1)); task.Start(); // Hot task (started task) should always be returned. return task; }

พวกเราพูดถึงการรอคอยหรือ async? ไม่โทรตามวิธีข้างต้นแล้วคุณจะได้งานที่สามารถตรวจสอบได้ คุณรู้อยู่แล้วว่างานที่ส่งกลับ .. จำนวนเต็ม

  • การเรียกใช้งานนั้นยุ่งยากเล็กน้อยและนั่นคือเมื่อคำหลักเริ่มปรากฏขึ้น ให้เราเรียก MethodTask ()

    internal static async Task<int> MethodAsync(int arg0, int arg1) { int result = await HelperMethods.MethodTask(arg0, arg1); return result; }

รหัสเดียวกันข้างต้นเพิ่มเป็นภาพด้านล่าง: ป้อนคำอธิบายรูปภาพที่นี่

  1. เรากำลังรอให้งานเสร็จ ดังนั้นawait
  2. เนื่องจากเราใช้คอยเราต้องใช้async(ไวยากรณ์บังคับ)
  3. MethodAsync พร้อมด้วยAsyncคำนำหน้า (มาตรฐานการเข้ารหัส)

awaitเข้าใจง่าย แต่ส่วนที่เหลืออีกสอง ( async, Async) อาจไม่ใช่ :) ก็ควรทำให้คอมไพเลอร์เข้าใจได้มากกว่านี้อีกอ่านต่อที่นี่

มี 2 ​​ส่วน

  1. สร้าง 'งาน'
  2. สร้างน้ำตาลประโยคเพื่อเรียกใช้งาน ( await+async)

โปรดจำไว้ว่าเรามีโทรภายนอกเพื่อ AccessTheWebAsync () และโทรที่ไม่ได้หวงทั้ง ... คือมันต้องการที่เหมือนกันawait+asyncมากเกินไป และห่วงโซ่ยังคง แต่จะมีเสมอTaskที่ปลายด้านหนึ่ง

ไม่เป็นไร แต่ผู้พัฒนารายหนึ่งรู้สึกประหลาดใจที่พบ # 1 (งาน) หายไป ...

แบ่งปันความสับสนของผู้พัฒนา: 5 นาที

นักพัฒนาซอฟต์แวร์ทำผิดพลาดที่ไม่ใช้งานTaskแต่ก็ยังใช้งานได้! พยายามที่จะเข้าใจคำถามและเพียงแค่คำตอบที่ได้รับการยอมรับให้ที่นี่ หวังว่าคุณจะได้อ่านและเข้าใจอย่างเต็มที่ บทสรุปคือเราอาจไม่เห็น / ใช้งาน 'งาน' แต่นำไปใช้ที่ไหนสักแห่งในระดับผู้ปกครอง ในตัวอย่างของเราที่เรียกว่าบิวด์ที่สร้างไว้แล้วMethodAsync()นั้นเป็นวิธีที่ง่ายกว่าการใช้เมธอดนั้นด้วยTask( MethodTask()) ตัวเราเอง นักพัฒนาส่วนใหญ่พบว่าเป็นการยากที่จะนำหน้าTasksในขณะที่แปลงรหัสเป็นแบบอะซิงโครนัส

เคล็ดลับ: ลองค้นหาการใช้ Async ที่มีอยู่ (เช่นMethodAsyncหรือToListAsync) เพื่อเอาท์ซอร์สความยากลำบาก ดังนั้นเราจะต้องจัดการกับ Async และรอ (ซึ่งเป็นเรื่องง่ายและค่อนข้างคล้ายกับรหัสปกติ)

ปัญหา: เปลี่ยนการใช้งานรหัสโลกปกติเป็นการดำเนินการ Async อย่างรวดเร็ว: 2 นาที

บรรทัดรหัสที่แสดงด้านล่างในชั้นข้อมูลเริ่มแตก (หลายแห่ง) เพราะเราอัปเดตโค้ดบางส่วนของเราจาก. Net framework 4.2. * เป็น. Net core เราต้องแก้ไขสิ่งนี้ใน 1 ชั่วโมงทั่วแอปพลิเคชัน!

var myContract = query.Where(c => c.ContractID == _contractID).First();

easypeasy!

  1. เราติดตั้งแพคเกจ nuget EntityFramework เพราะมันมี QueryableExtensions หรืออีกนัยหนึ่งก็ไม่ดำเนินการ Async (งาน) เพื่อให้เราสามารถอยู่รอดได้ด้วยง่ายAsyncและawaitในรหัส
  2. namespace = Microsoft.EntityFrameworkCore

สายรหัสโทรศัพท์เปลี่ยนไปเช่นนี้

var myContract = await query.Where(c => c.ContractID == _contractID).FirstAsync();
  1. ลายเซ็นวิธีเปลี่ยนจาก

    Contract GetContract(int contractnumber)

    ถึง

    async Task<Contract> GetContractAsync(int contractnumber)

  2. วิธีการโทรก็ได้รับผลกระทบด้วย: GetContractAsync(123456);ถูกเรียกว่าGetContractAsync(123456).Result;

  3. เราเปลี่ยนทุกที่ใน 30 นาที!

แต่สถาปนิกบอกเราว่าอย่าใช้ห้องสมุด EntityFramework เพียงแค่นี้! โอ๊ะ! ละคร! จากนั้นเราทำการปรับใช้งานแบบกำหนดเอง (yuk) ซึ่งคุณจะรู้ได้อย่างไร ยังง่าย! .. ยังคง yuk ..

ไปที่ไหนต่อไป? มีวิดีโอด่วนที่ยอดเยี่ยมที่เราสามารถรับชมเกี่ยวกับการแปลงการโทรแบบซิงโครนัสเป็นอะซิงโครนัสใน ASP.Net Coreซึ่งอาจเป็นไปได้ที่ทิศทางที่เราจะอ่านหลังจากนี้


คำตอบที่ยอดเยี่ยม! นี้ช่วยให้ฉันตัน
cklimowski

1
คำตอบที่ดี คุณอาจต้องการแก้ไขสิ่งเล็ก ๆ สองสามอย่างเช่น: (a) กล่าวถึง ".Net framework 4.2" (ไม่มีรุ่นดังกล่าวที่ฉันรู้ว่ามีอยู่) (b) ปลอกใน EntityFrameWork => EntityFramework
immitev

15

คำตอบทั้งหมดที่นี่ใช้Task.Delay()หรือasyncฟังก์ชั่นอื่น ๆ ในตัว แต่นี่คือตัวอย่างของฉันที่ไม่ใช้asyncฟังก์ชั่นเหล่านั้น:

// Starts counting to a large number and then immediately displays message "I'm counting...". 
// Then it waits for task to finish and displays "finished, press any key".
static void asyncTest ()
{
    Console.WriteLine("Started asyncTest()");
    Task<long> task = asyncTest_count();
    Console.WriteLine("Started counting, please wait...");
    task.Wait(); // if you comment this line you will see that message "Finished counting" will be displayed before we actually finished counting.
    //Console.WriteLine("Finished counting to " + task.Result.ToString()); // using task.Result seems to also call task.Wait().
    Console.WriteLine("Finished counting.");
    Console.WriteLine("Press any key to exit program.");
    Console.ReadLine();
}

static async Task<long> asyncTest_count()
{
    long k = 0;
    Console.WriteLine("Started asyncTest_count()");
    await Task.Run(() =>
    {
        long countTo = 100000000;
        int prevPercentDone = -1;
        for (long i = 0; i <= countTo; i++)
        {
            int percentDone = (int)(100 * (i / (double)countTo));
            if (percentDone != prevPercentDone)
            {
                prevPercentDone = percentDone;
                Console.Write(percentDone.ToString() + "% ");
            }

            k = i;
        }
    });
    Console.WriteLine("");
    Console.WriteLine("Finished asyncTest_count()");
    return k;
}

2
ขอบคุณ! คำตอบแรกที่ใช้งานได้จริงแทนที่จะรอ
Jeffnl

ขอบคุณสำหรับการแสดงtask.Wait();และวิธีการที่สามารถใช้เพื่อหลีกเลี่ยง async / คอยนรก: P
encoder

12

คำตอบนี้มีวัตถุประสงค์เพื่อให้ข้อมูลเฉพาะกับ ASP.NET

ด้วยการใช้ async / await ในตัวควบคุม MVC มันเป็นไปได้ที่จะเพิ่มการใช้เธรดพูลและให้ปริมาณงานที่ดีขึ้นตามที่อธิบายไว้ในบทความด้านล่าง

http://www.asp.net/mvc/tutorials/mvc-4/using-asynchronous-methods-in-aspnet-mvc-4

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


12

Async & รอคำอธิบายง่ายๆ

อะนาล็อกง่าย

คน ๆ หนึ่งอาจรอรถไฟตอนเช้า นี่คือทั้งหมดที่พวกเขาทำเพราะนี่เป็นภารกิจหลักที่พวกเขากำลังปฏิบัติอยู่ (การเขียนโปรแกรมแบบซิงโครนัส (ปกติคุณทำอะไร!)

บุคคลอื่นอาจรอรถไฟตอนเช้าในขณะที่สูบบุหรี่แล้วดื่มกาแฟ (การเขียนโปรแกรมแบบอะซิงโครนัส)

การเขียนโปรแกรมแบบอะซิงโครนัสคืออะไร?

การเขียนโปรแกรมแบบอะซิงโครนัสคือที่โปรแกรมเมอร์จะเลือกรันโค้ดบางส่วนของเขาบนเธรดแยกต่างหากจากเธรดหลักของการดำเนินการจากนั้นแจ้งเธรดหลักเมื่อเสร็จสิ้น

คำหลัก async ทำอะไรได้จริง?

คำนำหน้า async คำนำหน้าชื่อวิธีการเช่น

async void DoSomething(){ . . .

อนุญาตให้โปรแกรมเมอร์ใช้คีย์เวิร์ด await เมื่อเรียกภารกิจอะซิงโครนัส นั่นคือทั้งหมดที่มันทำ

ทำไมสิ่งนี้จึงสำคัญ

ในระบบซอฟต์แวร์จำนวนมากเธรดหลักถูกสงวนไว้สำหรับการดำเนินการเฉพาะที่เกี่ยวข้องกับส่วนติดต่อผู้ใช้ หากฉันใช้อัลกอริทึมแบบเรียกซ้ำที่ซับซ้อนมากซึ่งใช้เวลา 5 วินาทีในการดำเนินการบนคอมพิวเตอร์ของฉัน แต่ฉันกำลังเรียกใช้งานนี้บนเธรดหลัก (เธรด UI) เมื่อผู้ใช้พยายามคลิกที่สิ่งใด ๆ บนแอปพลิเคชันของฉัน เนื่องจากเธรดหลักของฉันเข้าคิวและกำลังประมวลผลการดำเนินการมากเกินไป ด้วยเหตุนี้เธรดหลักไม่สามารถประมวลผลการคลิกเมาส์เพื่อรันเมธอดจากการคลิกปุ่ม

คุณใช้ Async และ Await เมื่อใด

ใช้คำหลักแบบอะซิงโครนัสในอุดมคติเมื่อคุณทำสิ่งที่ไม่เกี่ยวข้องกับส่วนต่อประสานผู้ใช้

ดังนั้นสมมติว่าคุณกำลังเขียนโปรแกรมที่ช่วยให้ผู้ใช้สามารถวาดภาพบนโทรศัพท์มือถือของพวกเขา แต่ทุก ๆ 5 วินาทีมันจะต้องตรวจสอบสภาพอากาศบนอินเทอร์เน็ต

เราควรจะรอการโทรจากหน่วยเลือกตั้งทุก 5 วินาทีเพื่อให้เครือข่ายได้รับสภาพอากาศเนื่องจากผู้ใช้แอปพลิเคชันจำเป็นต้องมีการโต้ตอบกับหน้าจอสัมผัสมือถือเพื่อวาดภาพสวย ๆ

คุณใช้ Async และ Await อย่างไร

จากตัวอย่างข้างบนต่อไปนี้เป็นโค้ดหลอกบางส่วนของวิธีการเขียน:

    //ASYNCHRONOUS
    //this is called using the await keyword every 5 seconds from a polling timer or something.

    async Task CheckWeather()
    {
        var weather = await GetWeather();
        //do something with the weather now you have it
    }

    async Task<WeatherResult> GetWeather()
    {

        var weatherJson = await CallToNetworkAddressToGetWeather();
        return deserializeJson<weatherJson>(weatherJson);
    }

    //SYNCHRONOUS
    //This method is called whenever the screen is pressed
    void ScreenPressed()
    {
        DrawSketchOnScreen();
    }

หมายเหตุเพิ่มเติม - อัปเดต

ฉันลืมที่จะพูดถึงในบันทึกย่อดั้งเดิมของฉันว่าใน C # คุณสามารถรอวิธีการที่อยู่ในงาน ตัวอย่างเช่นคุณอาจรอวิธีนี้:

// awaiting this will return a string.
// calling this without await (synchronously) will result in a Task<string> object.
async Task<string> FetchHelloWorld() {..

คุณไม่สามารถรอวิธีการที่ไม่ใช่งานเช่นนี้:

async string FetchHelloWorld() {..

รู้สึกอิสระที่จะดูซอร์สโค้ดสำหรับชั้นงานที่นี่


4
ขอบคุณที่สละเวลาเขียนสิ่งนี้
Prashant

10

Async / Await

จริง ๆ แล้ว Async / Await เป็นคู่ของคีย์เวิร์ดซึ่งเป็นเพียงน้ำตาลซินแทคติคสำหรับสร้างการโทรกลับของงานอะซิงโครนัส

ทำตามตัวอย่างการดำเนินการนี้:

    public static void DoSomeWork()
    {
        var task = Task.Run(() =>
        {
            // [RUNS ON WORKER THREAD]

            // IS NOT bubbling up due to the different threads
            throw new Exception();
            Thread.Sleep(2000);

            return "Hello";
        });

        // This is the callback
        task.ContinueWith((t) => {
            // -> Exception is swallowed silently
            Console.WriteLine("Completed");

            // [RUNS ON WORKER THREAD]
        });
    }

รหัสข้างต้นมีข้อเสียหลายประการ ข้อผิดพลาดจะไม่ถูกส่งต่อและเป็นการยากที่จะอ่าน แต่ Async และ Await เข้ามาช่วยเราออก:

    public async static void DoSomeWork()
    {
        var result = await Task.Run(() =>
        {
            // [RUNS ON WORKER THREAD]

            // IS bubbling up
            throw new Exception();
            Thread.Sleep(2000);

            return "Hello";
        });

        // every thing below is a callback 
        // (including the calling methods)

        Console.WriteLine("Completed");

    }

ต้องรอสายในวิธี Async นี่เป็นข้อดี:

  • ส่งคืนผลลัพธ์ของภารกิจ
  • สร้างการติดต่อกลับอัตโนมัติ
  • ตรวจสอบข้อผิดพลาดและช่วยให้พวกเขาฟองสบู่ใน callstack (เฉพาะการโทรที่รอสายใน callstack ไม่เกิน)
  • รอผล
  • ปลดปล่อยเธรดหลัก
  • รัน callback บนเธรดหลัก
  • ใช้ thread ผู้ทำงานจาก threadpool สำหรับภารกิจ
  • ทำให้โค้ดอ่านง่าย
  • และอีกมากมาย

หมายเหตุ : Async และ Await ใช้กับการโทรแบบอะซิงโครนัสเพื่อไม่ให้ทำการโทร คุณต้องใช้Task Libaryสำหรับสิ่งนี้เช่น Task.Run ()

นี่คือการเปรียบเทียบระหว่างการรอคอยและไม่มีใครรอการแก้ไขปัญหา

นี่คือโซลูชัน async ที่ไม่มี:

    public static long DoTask()
    {
        stopWatch.Reset();
        stopWatch.Start();

        // [RUNS ON MAIN THREAD]
        var task = Task.Run(() => {
            Thread.Sleep(2000);
            // [RUNS ON WORKER THREAD]
        });
        // goes directly further
        // WITHOUT waiting until the task is finished

        // [RUNS ON MAIN THREAD]

        stopWatch.Stop();
        // 50 milliseconds
        return stopWatch.ElapsedMilliseconds;
    }

นี่เป็นวิธี async:

    public async static Task<long> DoAwaitTask()
    {
        stopWatch.Reset();
        stopWatch.Start();

        // [RUNS ON MAIN THREAD]

        await Task.Run(() => {
            Thread.Sleep(2000);
            // [RUNS ON WORKER THREAD]
        });
        // Waits until task is finished

        // [RUNS ON MAIN THREAD]

        stopWatch.Stop();
        // 2050 milliseconds
        return stopWatch.ElapsedMilliseconds;
    }

คุณสามารถเรียกวิธีการแบบอะซิงก์ได้โดยไม่ต้องใช้คำสำคัญที่รอ แต่นี่หมายความว่าการยกเว้นใด ๆ ที่นี่จะถูกกลืนเข้าไปในโหมดการเปิดตัว:

    public static Stopwatch stopWatch { get; } = new Stopwatch();

    static void Main(string[] args)
    {
        Console.WriteLine("DoAwaitTask: " + DoAwaitTask().Result + " ms");
        // 2050 (2000 more because of the await)
        Console.WriteLine("DoTask: " + DoTask() + " ms");
        // 50
        Console.ReadKey();
    }

Async และ Await ไม่ได้มีไว้สำหรับการคำนวณแบบขนาน พวกเขาจะไม่บล็อกหัวข้อหลักของคุณ เมื่อมันเกี่ยวกับแอพพลิเคชั่น asp.net หรือ Windows การบล็อกเธรดหลักของคุณเนื่องจากการโทรผ่านเครือข่ายเป็นสิ่งที่ไม่ดี หากคุณทำเช่นนี้แอปของคุณจะไม่ตอบสนองหรือผิดพลาด

ตรวจสอบMS เอกสารสำหรับตัวอย่างเพิ่มเติม


9

บอกตามตรงฉันยังคิดว่าคำอธิบายที่ดีที่สุดคือคำเกี่ยวกับอนาคตและสัญญาใน Wikipedia: http://en.wikipedia.org/wiki/Futures_and_promises

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

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

คุณสามารถนำรูปแบบการจัดเรียงของอนาคตไปใช้กับอัตราผลตอบแทนใน C # 4.0 ได้ หากคุณต้องการที่จะรู้วิธีการทำงานว่าจะสามารถแนะนำลิงค์นี้ที่ไม่ได้งานที่ดี: http://code.google.com/p/fracture/source/browse/trunk/Squared/TaskLib/ อย่างไรก็ตามถ้าคุณเริ่มเล่นด้วยตัวคุณเองคุณจะสังเกตเห็นว่าคุณต้องการการสนับสนุนทางภาษาจริงๆหากคุณต้องการทำสิ่งดีๆทั้งหมด - ซึ่งเป็นสิ่งที่ Microsoft ทำ


8

ดูซอนี้https://dotnetfiddle.net/VhZdLU (และปรับปรุงถ้าเป็นไปได้) สำหรับการรันแอปพลิเคชันคอนโซลแบบง่ายซึ่งแสดงการใช้งานของTask, Task.WaitAll (), async และรอโอเปอเรเตอร์ในโปรแกรมเดียวกัน

ซอนี้ควรล้างแนวคิดวงจรการดำเนินการของคุณ

นี่คือตัวอย่างรหัส

using System;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {               
        var a = MyMethodAsync(); //Task started for Execution and immediately goes to Line 19 of the code. Cursor will come back as soon as await operator is met       
        Console.WriteLine("Cursor Moved to Next Line Without Waiting for MyMethodAsync() completion");
        Console.WriteLine("Now Waiting for Task to be Finished");       
        Task.WaitAll(a); //Now Waiting      
        Console.WriteLine("Exiting CommandLine");       
    }

    public static async Task MyMethodAsync()
    {
        Task<int> longRunningTask = LongRunningOperation();
        // independent work which doesn't need the result of LongRunningOperationAsync can be done here
        Console.WriteLine("Independent Works of now executes in MyMethodAsync()");
        //and now we call await on the task 
        int result = await longRunningTask;
        //use the result 
        Console.WriteLine("Result of LongRunningOperation() is " + result);
    }

    public static async Task<int> LongRunningOperation() // assume we return an int from this long running operation 
    {
        Console.WriteLine("LongRunningOperation() Started");
        await Task.Delay(2000); // 2 second delay
        Console.WriteLine("LongRunningOperation() Finished after 2 Seconds");
        return 1;
    }   

}

ติดตามมาจากหน้าต่างแสดงผล: ป้อนคำอธิบายรูปภาพที่นี่


3
public static void Main(string[] args)
{
    string result = DownloadContentAsync().Result;
    Console.ReadKey();
}

// You use the async keyword to mark a method for asynchronous operations.
// The "async" modifier simply starts synchronously the current thread. 
// What it does is enable the method to be split into multiple pieces.
// The boundaries of these pieces are marked with the await keyword.
public static async Task<string> DownloadContentAsync()// By convention, the method name ends with "Async
{
    using (HttpClient client = new HttpClient())
    {
        // When you use the await keyword, the compiler generates the code that checks if the asynchronous operation is finished.
        // If it is already finished, the method continues to run synchronously.
        // If not completed, the state machine will connect a continuation method that must be executed WHEN the Task is completed.


        // Http request example. 
        // (In this example I can set the milliseconds after "sleep=")
        String result = await client.GetStringAsync("http://httpstat.us/200?sleep=1000");

        Console.WriteLine(result);

        // After completing the result response, the state machine will continue to synchronously execute the other processes.


        return result;
    }
}

3

ในระดับที่สูงขึ้น:

1) คำหลัก Async เปิดใช้งานการรอคอยและนั่นคือทั้งหมดที่ทำได้ คีย์เวิร์ด Async ไม่ได้รันเมธอดในเธรดแยกต่างหาก เมธอด f async เริ่มต้นทำงานแบบซิงโครนัสจนกว่าจะพบการรอคอยในงานที่ต้องใช้เวลามาก

2) คุณสามารถรอวิธีการส่งคืนงานหรืองานประเภท T คุณไม่สามารถรอวิธีการ async void

3) ช่วงเวลาที่เธรดหลักพบกำลังรองานที่ต้องใช้เวลามากหรือเมื่อเริ่มงานจริงเธรดหลักจะส่งกลับไปยังผู้เรียกของวิธีการปัจจุบัน

4) หากเธรดหลักเห็นการรอคอยสำหรับงานที่ยังคงดำเนินการอยู่จะไม่รอและส่งกลับไปยังผู้เรียกใช้เมธอดปัจจุบัน ด้วยวิธีนี้แอปพลิเคชันยังคงตอบสนองได้

5) รองานการประมวลผลตอนนี้จะรันบนเธรดแยกต่างหากจากเธรดพูล

6) เมื่องานที่รอนี้เสร็จสมบูรณ์โค้ดด้านล่างทั้งหมดจะถูกดำเนินการโดยเธรดแยกต่างหาก

ด้านล่างนี้คือตัวอย่างรหัส ดำเนินการแล้วตรวจสอบ id ของเธรด

using System;
using System.Threading;
using System.Threading.Tasks;

namespace AsyncAwaitDemo
{
    class Program
    {
        public static async void AsynchronousOperation()
        {
            Console.WriteLine("Inside AsynchronousOperation Before AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
            //Task<int> _task = AsyncMethod();
            int count = await AsyncMethod();

            Console.WriteLine("Inside AsynchronousOperation After AsyncMethod Before Await, Thread Id: " + Thread.CurrentThread.ManagedThreadId);

            //int count = await _task;

            Console.WriteLine("Inside AsynchronousOperation After AsyncMethod After Await Before DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);

            DependentMethod(count);

            Console.WriteLine("Inside AsynchronousOperation After AsyncMethod After Await After DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
        }

        public static async Task<int> AsyncMethod()
        {
            Console.WriteLine("Inside AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
            int count = 0;

            await Task.Run(() =>
            {
                Console.WriteLine("Executing a long running task which takes 10 seconds to complete, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(20000);
                count = 10;
            });

            Console.WriteLine("Completed AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);

            return count;
        }       

        public static void DependentMethod(int count)
        {
            Console.WriteLine("Inside DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId + ". Total count is " + count);
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Started Main method, Thread Id: " + Thread.CurrentThread.ManagedThreadId);

            AsynchronousOperation();

            Console.WriteLine("Completed Main method, Thread Id: " + Thread.CurrentThread.ManagedThreadId);

            Console.ReadKey();
        }

    }
}

2

วิธีที่ฉันเข้าใจก็คือควรมีคำที่สามที่เพิ่มเข้ามาในการผสมผสาน: Task.

Async เป็นเพียง qualifier ที่คุณใส่ในวิธีการของคุณเพื่อบอกว่ามันเป็นวิธีการแบบอะซิงโครนัส

Taskคือการกลับมาของasyncฟังก์ชั่น มันรันแบบอะซิงโครนัส

คุณawaitเป็นงาน เมื่อการเรียกใช้โค้ดถึงบรรทัดนี้การควบคุมจะกระโดดกลับไปที่ผู้โทรของฟังก์ชั่นดั้งเดิมโดยรอบของคุณ

หากคุณกำหนดการส่งคืนของasyncฟังก์ชัน (เช่นTask) ให้กับตัวแปรเมื่อการเรียกใช้โค้ดมาถึงบรรทัดนี้มันจะดำเนินต่อไปผ่านบรรทัดนั้นในฟังก์ชันโดยรอบขณะที่การTaskประมวลผลแบบอะซิงโครนัส


1

ใช้พวกมันเท่ากับวางไข่เธรดพื้นหลังเพื่อดำเนินการตรรกะระยะเวลานาน?

บทความนี้MDSN: การเขียนโปรแกรมแบบอะซิงโครนัสกับ async และรอ (C #)อธิบายอย่างชัดเจน:

คำหลักแบบ async และ waitit จะไม่ทำให้เกิดการสร้างเธรดเพิ่มเติม เมธอด Async ไม่ต้องการมัลติเธรดเนื่องจากเมธอด async ไม่ทำงานบนเธรดของตัวเอง วิธีการทำงานกับบริบทการซิงโครไนซ์ปัจจุบันและใช้เวลาในเธรดเฉพาะเมื่อวิธีการใช้งานอยู่


1

ในรหัสต่อไปนี้เมธอด HttpClient GetByteArrayAsync ส่งคืนภารกิจคือ getContentsTask งานคือสัญญาในการสร้างอาร์เรย์ไบต์จริงเมื่องานเสร็จสมบูรณ์ โอเปอเรเตอร์ที่รอคอยจะถูกนำไปใช้กับ getContentsTask เพื่อระงับการดำเนินการใน SumPageSizesAsync จนกว่า getContentsTask จะเสร็จสมบูรณ์ ในระหว่างนี้การควบคุมจะถูกส่งคืนไปยังผู้เรียก SumPageSizesAsync เมื่อ getContentsTask เสร็จสิ้นนิพจน์ที่รอคอยจะประเมินเป็นอาร์เรย์ไบต์

private async Task SumPageSizesAsync()
{
    // To use the HttpClient type in desktop apps, you must include a using directive and add a 
    // reference for the System.Net.Http namespace.
    HttpClient client = new HttpClient();
    // . . .
    Task<byte[]> getContentsTask = client.GetByteArrayAsync(url);
    byte[] urlContents = await getContentsTask;

    // Equivalently, now that you see how it works, you can write the same thing in a single line.
    //byte[] urlContents = await client.GetByteArrayAsync(url);
    // . . .
}

1

ด้านล่างเป็นโค้ดที่อ่านไฟล์ excel โดยเปิดไดอะล็อกจากนั้นใช้ async และรอเรียกใช้อะซิงโครนัสโค้ดที่อ่านทีละบรรทัดจาก excel และผูกกับกริด

namespace EmailBillingRates
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            lblProcessing.Text = "";
        }

        private async void btnReadExcel_Click(object sender, EventArgs e)
        {
            string filename = OpenFileDialog();

            Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
            Microsoft.Office.Interop.Excel.Workbook xlWorkbook = xlApp.Workbooks.Open(filename);
            Microsoft.Office.Interop.Excel._Worksheet xlWorksheet = xlWorkbook.Sheets[1];
            Microsoft.Office.Interop.Excel.Range xlRange = xlWorksheet.UsedRange;
            try
            {
                Task<int> longRunningTask = BindGrid(xlRange);
                int result = await longRunningTask;

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            }
            finally
            {
                //cleanup  
               // GC.Collect();
                //GC.WaitForPendingFinalizers();

                //rule of thumb for releasing com objects:  
                //  never use two dots, all COM objects must be referenced and released individually  
                //  ex: [somthing].[something].[something] is bad  

                //release com objects to fully kill excel process from running in the background  
                Marshal.ReleaseComObject(xlRange);
                Marshal.ReleaseComObject(xlWorksheet);

                //close and release  
                xlWorkbook.Close();
                Marshal.ReleaseComObject(xlWorkbook);

                //quit and release  
                xlApp.Quit();
                Marshal.ReleaseComObject(xlApp);
            }

        }

        private void btnSendEmail_Click(object sender, EventArgs e)
        {

        }

        private string OpenFileDialog()
        {
            string filename = "";
            OpenFileDialog fdlg = new OpenFileDialog();
            fdlg.Title = "Excel File Dialog";
            fdlg.InitialDirectory = @"c:\";
            fdlg.Filter = "All files (*.*)|*.*|All files (*.*)|*.*";
            fdlg.FilterIndex = 2;
            fdlg.RestoreDirectory = true;
            if (fdlg.ShowDialog() == DialogResult.OK)
            {
                filename = fdlg.FileName;
            }
            return filename;
        }

        private async Task<int> BindGrid(Microsoft.Office.Interop.Excel.Range xlRange)
        {
            lblProcessing.Text = "Processing File.. Please wait";
            int rowCount = xlRange.Rows.Count;
            int colCount = xlRange.Columns.Count;

            // dt.Column = colCount;  
            dataGridView1.ColumnCount = colCount;
            dataGridView1.RowCount = rowCount;

            for (int i = 1; i <= rowCount; i++)
            {
                for (int j = 1; j <= colCount; j++)
                {
                    //write the value to the Grid  
                    if (xlRange.Cells[i, j] != null && xlRange.Cells[i, j].Value2 != null)
                    {
                         await Task.Delay(1);
                         dataGridView1.Rows[i - 1].Cells[j - 1].Value =  xlRange.Cells[i, j].Value2.ToString();
                    }

                }
            }
            lblProcessing.Text = "";
            return 0;
        }
    }

    internal class async
    {
    }
}

0

คำตอบที่นี่มีประโยชน์เป็นแนวทางทั่วไปเกี่ยวกับการรอ / async นอกจากนี้ยังมีรายละเอียดบางอย่างเกี่ยวกับการรอคอย / การซิงค์แบบใช้สาย ฉันต้องการแบ่งปันประสบการณ์จริงกับคุณที่คุณควรรู้ก่อนใช้รูปแบบการออกแบบนี้

คำว่า "คอย" เป็นตัวอักษรดังนั้นสิ่งที่คุณเรียกด้ายบนจะรอผลของวิธีการก่อนที่จะดำเนินการต่อ ในเบื้องหน้าหัวข้อนี้เป็นภัยพิบัติ เธรดเบื้องหน้ามีภาระในการสร้างแอปของคุณรวมถึงมุมมอง, มุมมองโมเดล, แอนิเมชั่นเริ่มต้นและสิ่งอื่น ๆ ที่คุณผูกติดอยู่กับองค์ประกอบเหล่านั้น ดังนั้นเมื่อคุณรอเธรดเบื้องหน้าคุณจะหยุดแอพ ผู้ใช้รอและรอเมื่อไม่มีอะไรเกิดขึ้น สิ่งนี้ให้ประสบการณ์ผู้ใช้ในเชิงลบ

คุณสามารถรอเธรดพื้นหลังได้อย่างแน่นอนโดยใช้วิธีการที่หลากหลาย:

Device.BeginInvokeOnMainThread(async () => { await AnyAwaitableMethod(); });

// Notice that we do not await the following call, 
// as that would tie it to the foreground thread.
try
{
Task.Run(async () => { await AnyAwaitableMethod(); });
}
catch
{}

รหัสที่สมบูรณ์สำหรับคำพูดเหล่านี้เป็นสิ่งที่https://github.com/marcusts/xamarin-forms-annoyances ดูโซลูชันที่ชื่อ AwaitAsyncAntipattern.sln

ไซต์ GitHub ยังมีลิงก์ไปยังการอภิปรายรายละเอียดเพิ่มเติมในหัวข้อนี้


1
จากสิ่งที่ฉันเข้าใจasync / awaitเป็นน้ำตาลเชิงประโยคสำหรับการโทรกลับไม่มีอะไรเกี่ยวข้องกับเธรด msdn.microsoft.com/en-us/magazine/hh456401.aspx สำหรับรหัสที่ไม่ใช่ CPU ที่ถูกผูกไว้เช่นกำลังรออินพุตหรือความล่าช้า Task.Runควรใช้สำหรับรหัสที่ผูกกับ CPU เท่านั้นblog.stephencleary.com/2013/10/…
geometrikal

The term "await" is literal, so whatever thread you call it on will wait for the result of the method before continuing.สิ่งนี้ไม่เป็นความจริง - บางทีคุณอาจหมายถึง Task.Wait ()? เมื่อคุณใช้awaitมันจะตั้งค่าส่วนที่เหลือของวิธีการที่จะดำเนินการต่อเมื่อสิ่งที่คุณรอเสร็จสมบูรณ์ มันออกจากวิธีการที่คุณใช้ในดังนั้นผู้โทรสามารถดำเนินการต่อ จากนั้นเมื่อบรรทัดที่รอคอยเสร็จสมบูรณ์จริง ๆ แล้วมันจะเสร็จสิ้นส่วนที่เหลือของวิธีการนั้นในเธรดบางตัว (โดยทั่วไปคือเธรดผู้ปฏิบัติงาน)
Don Cheadle

@geometrikal ที่แกนกลางasync/awaitเป็นเรื่องเกี่ยวกับการเพิ่ม . NET Threads เมื่อคุณawaitทำการดำเนินการแบบ async อย่างแท้จริง (เช่น File.WriteAsync ของ. NET) มันจะระงับส่วนที่เหลือของวิธีการที่คุณใช้awaitในดังนั้นผู้โทรสามารถดำเนินการต่อและอาจเสร็จสิ้นตามวัตถุประสงค์ ไม่มีการบล็อกเธรดหรือรอการawaitดำเนินการ -ed เมื่อการดำเนินการที่คุณดำเนินการawaitเสร็จสิ้นแล้วส่วนที่เหลือของasync/awaitวิธีการนี้จะถูกวางไว้บนเธรดและดำเนินการ (คล้ายกับแนวคิดการโทรกลับ)
Don Cheadle
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.