async รอพฤติกรรม [ซ้ำ]

Nov 19 2020

ฉันกำลังทดลองกับ async-await และฉันก็เจอพฤติกรรมแปลก ๆ นี้อย่างน้อยก็สำหรับฉัน
ฉันสร้างวิธีการสามวิธีที่จำลองงานที่ต้องใช้เวลานาน
พิจารณาตัวจัดการการคลิกปุ่มสองปุ่ม:
สำหรับ button1_click เวลาที่ผ่านไปอยู่ที่ประมาณ 6000ms ในขณะที่ button2_click ประมาณ 3000ms
ฉันไม่สามารถคาดเดาได้ว่าทำไมสิ่งนี้ถึงเกิดขึ้นเช่น 6000ms กับ 3000ms

    private async Task<string> TaskOne()
    {
        await Task.Delay(1000);
        return "task one";
    }

    private async Task<string> TaskTwo()
    {
        await Task.Delay(2000);
        return "task two";
    }

    private async Task<string> TaskThree()
    {
        await Task.Delay(3000);
        return "task three";
    }
    
    //time elapsed = 6000+ms
    private async void button1_Click(object sender, EventArgs e)
    {
        var watch = new Stopwatch();
        watch.Start();

        await TaskOne();
        await TaskTwo();
        await TaskThree();
        
        watch.Stop();
        textBox3.Text = watch.ElapsedMilliseconds.ToString();
    }

    //time elapsed = 3000+ms
    private async void button2_Click(object sender, EventArgs e)
    {
        var watch = new Stopwatch();
        watch.Start();

        var taskOne = TaskOne();
        var taskTwo = TaskTwo();
        var taskThree = TaskThree();

        await taskOne;
        await taskTwo;
        await taskThree;
        
        watch.Stop();
        textBox3.Text = watch.ElapsedMilliseconds.ToString();
    }

คำตอบ

7 dogyear Nov 20 2020 at 02:57

ในกรณีนี้:

 await TaskOne();
 await TaskTwo();
 await TaskThree();

TaskTwo () ไม่สามารถเริ่มทำงานได้จนกว่า TaskOne () จะเสร็จสมบูรณ์เนื่องจากคุณกำลังรออยู่ ในทำนองเดียวกัน TaskThree () ไม่สามารถเริ่มทำงานได้จนกว่า TaskTwo () จะเสร็จสมบูรณ์เนื่องจากการรอ

ในครั้งต่อไป:

var taskOne = TaskOne();
var taskTwo = TaskTwo();
var taskThree = TaskThree();

await taskOne;
await taskTwo;
await taskThree;

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

7 00110001 Nov 20 2020 at 03:00

สรุป

จุดรับกลับบ้านที่นี่ (และเป็นความเข้าใจผิดที่พบบ่อยมากเป็นอย่างอื่น) นั่นawaitหมายความว่า"รอ"จริงๆ


รอตัวดำเนินการ (อ้างอิง C #)

เน้นของฉัน

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

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

ตัวดำเนินการ await ไม่บล็อกเธรดที่ประเมินเมธอด async เมื่อตัวดำเนินการ await ระงับเมธอด async ที่แนบมาตัวควบคุมจะส่งกลับไปยังผู้เรียกของเมธอด


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

await TaskOne();    // start, do something and wait for it
await TaskTwo();    // start, do something and wait for it
await TaskThree();  // start, do  something and wait for it

ตัวอย่างที่สองของคุณ คุณกำลังเริ่มงาน 3 อย่าง (ร้อนแรง) เป็นหลักจากนั้นรอให้เสร็จทีละอย่าง กล่าวคือพวกเขาทำงานพร้อมกันและรอคอยตามลำดับ

คือคุณพูดกับเพื่อน 3 คนไปทำของแล้วรอคนแรกกลับมาแล้วคนที่สองก็คนที่สาม มีประสิทธิภาพกว่ามาก ... ไม่มีเพื่อนที่น่ารำคาญอยู่ใกล้ ๆ จนกว่าคนก่อนหน้าจะกลับมา

แม้ว่างานที่สองจะเสร็จสิ้นก่อนงานแรกคุณยังคงมีผลรองานแรกก่อนที่จะดูสถานะที่เสร็จสมบูรณ์ของงานที่สองเป็นต้น

var taskOne = TaskOne();     // start, do something
var taskTwo = TaskTwo();     // start, do something
var taskThree = TaskThree(); // start, do something

// all 3 tasks are started

await taskOne;   // wait for it
await taskTwo;   // wait for it
await taskThree; // wait for it

หรือในทำนองเดียวกันคุณสามารถใช้ Task.WhenAll

สร้างงานที่จะเสร็จสมบูรณ์เมื่องานที่ให้มาทั้งหมดเสร็จสิ้น

var taskOne = TaskOne();     // start, do something
var taskTwo = TaskTwo();     // start, do something
var taskThree = TaskThree(); // start, do something

// wait for them all to finish!
await Task.WhenAll(taskOne, taskTwo, taskThree);