จะรอให้วิธีการ async เสร็จสมบูรณ์ได้อย่างไร


138

ฉันกำลังเขียนแอปพลิเคชัน WinForms ที่ถ่ายโอนข้อมูลไปยังอุปกรณ์ USB HID class ใบสมัครของฉันใช้ที่ดีเยี่ยม v6.0 ห้องสมุดทั่วไป HID ซึ่งสามารถพบได้ที่นี่ โดยสรุปเมื่อฉันต้องการเขียนข้อมูลไปยังอุปกรณ์นี่คือรหัสที่ถูกเรียก:

private async void RequestToSendOutputReport(List<byte[]> byteArrays)
{
    foreach (byte[] b in byteArrays)
    {
        while (condition)
        {
            // we'll typically execute this code many times until the condition is no longer met
            Task t = SendOutputReportViaInterruptTransfer();
            await t;
        }

        // read some data from device; we need to wait for this to return
        RequestToGetInputReport();
    }
}

เมื่อรหัสของฉันหลุดจากลูปขณะที่ฉันต้องอ่านข้อมูลบางอย่างจากอุปกรณ์ อย่างไรก็ตามอุปกรณ์ไม่สามารถตอบสนองได้ทันทีดังนั้นฉันต้องรอให้การโทรนี้กลับมาก่อนที่ฉันจะดำเนินการต่อ ตามที่มีอยู่ในปัจจุบัน RequestToGetInputReport () จะประกาศดังนี้:

private async void RequestToGetInputReport()
{
    // lots of code prior to this
    int bytesRead = await GetInputReportViaInterruptTransfer();
}

สำหรับสิ่งที่มีค่าการประกาศสำหรับ GetInputReportViaInterruptTransfer () มีลักษณะดังนี้:

internal async Task<int> GetInputReportViaInterruptTransfer()

น่าเสียดายที่ฉันไม่คุ้นเคยกับการทำงานของเทคโนโลยี async / await ใหม่ใน. NET 4.5 ก่อนหน้านี้ฉันได้อ่านเล็กน้อยเกี่ยวกับคำหลักที่รอคอยและนั่นทำให้ฉันรู้สึกว่าการเรียกไปยัง GetInputReportViaInterruptTransfer () ภายใน RequestToGetInputReport () จะรอ (และอาจจะใช่หรือไม่) แต่ดูเหมือนจะไม่เป็นการเรียกร้องให้ RequestToGetInputReport () ตัวเองกำลังรอเพราะฉันดูเหมือนจะกลับเข้าห่วงในขณะที่เกือบจะในทันที?

ทุกคนสามารถอธิบายพฤติกรรมที่ฉันเห็นได้หรือไม่

คำตอบ:


131

async voidหลีกเลี่ยงการ มีวิธีการของคุณกลับมาแทนTask voidจากนั้นคุณสามารถawaitพวกเขา

แบบนี้:

private async Task RequestToSendOutputReport(List<byte[]> byteArrays)
{
    foreach (byte[] b in byteArrays)
    {
        while (condition)
        {
            // we'll typically execute this code many times until the condition is no longer met
            Task t = SendOutputReportViaInterruptTransfer();
            await t;
        }

        // read some data from device; we need to wait for this to return
        await RequestToGetInputReport();
    }
}

private async Task RequestToGetInputReport()
{
    // lots of code prior to this
    int bytesRead = await GetInputReportViaInterruptTransfer();
}

1
ดีมากขอบคุณ ฉันถูกเกาหัวของฉันเกี่ยวกับปัญหาที่คล้ายกันและความแตกต่างคือการเปลี่ยนvoidไปTaskเช่นเดียวกับท่านได้กล่าวว่า
Jeremy

8
มันเป็นเรื่องเล็กน้อย แต่การทำตามอนุสัญญาทั้งสองวิธีควรมีการเพิ่ม Async ให้กับชื่อของพวกเขาเช่น RequestToGetInputReportAsync ()
tymtam

6
และถ้าผู้โทรเป็นฟังก์ชั่นหลัก?
symbiont

14
@symbiont: ใช้แล้วGetAwaiter().GetResult()
Stephen Cleary

4
@AhmedSalah Taskแสดงให้เห็นถึงการดำเนินการของวิธีการ - เพื่อให้returnค่าถูกวางไว้บนและข้อยกเว้นจะอยู่ในTask.Result Task.Exceptionด้วยvoidคอมไพเลอร์ไม่มีที่ไหนเลยที่จะวางข้อยกเว้นดังนั้นพวกเขาจึงเพิ่งฟื้นคืนชีพขึ้นมาบนเธรดพูลเธรด
Stephen Cleary

229

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

คำตอบสำหรับคำถามเฉพาะในชื่อคำถามของคุณคือการบล็อกasyncค่าส่งคืนของวิธีการ (ซึ่งควรเป็นประเภทTaskหรือTask<T>) โดยการเรียกWaitวิธีการที่เหมาะสม:

public static async Task<Foo> GetFooAsync()
{
    // Start asynchronous operation(s) and return associated task.
    ...
}

public static Foo CallGetFooAsyncAndWaitOnResult()
{
    var task = GetFooAsync();
    task.Wait(); // Blocks current thread until GetFooAsync task completes
                 // For pedagogical use only: in general, don't do this!
    var result = task.Result;
    return result;
}

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

คำตอบที่"คอย" ไม่รอให้การโทรเสร็จสมบูรณ์มีคำอธิบายที่ละเอียดกว่านี้หลายคำ

ในขณะเดียวกันคำแนะนำ @Stephen Cleary เกี่ยวกับการasync voidระงับ คำอธิบายที่ดีอื่น ๆ สำหรับสาเหตุที่สามารถพบได้ที่http://www.tonicodes.net/blog/why-you-should-almost-never-write-void-asynchronous-methods/และhttps://jaylee.org/archive/ 2012 / 08/07 / C-คม async เคล็ดลับและเทคนิคส่วน-2-async-void.html


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

@ Richard Cook - ขอบคุณมากสำหรับคำอธิบายเพิ่มเติม!
bmt22033

10
นี่ควรเป็นคำตอบที่ยอมรับได้เนื่องจากมันจะตอบคำถามที่แท้จริงได้ชัดเจนยิ่งขึ้น (เช่นวิธีบล็อก thread-wise ในวิธีการแบบ async)
csvan

ทางออกที่ดีที่สุดคือรอ async จนงานเสร็จสมบูรณ์คือ var result = Task.Run (async () => {กลับมารอ yourMethod ();}) ผลลัพธ์;
Ram chittala

70

ทางออกที่ดีที่สุดที่จะรอ AsynMethod จนกว่างานจะเสร็จสมบูรณ์

var result = Task.Run(async() => await yourAsyncMethod()).Result;

15
หรือนี่สำหรับ async ของคุณ "void": Task.Run (async () => {รอ yourAsyncMethod ();}) รอ ();
JiříHerník

1
yourAsyncMethod () มีประโยชน์อย่างไร?
Justin J Stark

1
เพียงแค่เข้าถึงคุณสมบัติ. Result ไม่ได้รอจนกว่างานจะดำเนินการเสร็จสิ้น ในความเป็นจริงฉันเชื่อว่ามันจะส่งข้อยกเว้นหากถูกเรียกก่อนที่งานจะเสร็จสมบูรณ์ ฉันคิดว่าข้อดีของการรวมไว้ในการเรียก Task.Run () คือ Richard Cook กล่าวถึงด้านล่างว่า "คอย" ไม่รอให้ภารกิจเสร็จสมบูรณ์ แต่ใช้การเรียก. (()) บล็อกเธรดพูลทั้งหมดของคุณ . สิ่งนี้จะช่วยให้คุณสามารถเรียกใช้เมธอด async ในเธรดแยกกัน สับสนเล็กน้อย แต่ก็มี
Lucas Leblanc

มีความสุขมากในผลลัพธ์ที่นั่นสิ่งที่ฉันต้องการ
เจอร์รี่

การแจ้งเตือนอย่างรวดเร็ว ECMA7 featurelikeike assync () หรือรอทำงานในสภาพแวดล้อมก่อน ECMA7
Mbotet

0

นี่คือวิธีแก้ปัญหาโดยใช้ธง:

//outside your event or method, but inside your class
private bool IsExecuted = false;

private async Task MethodA()
{

//Do Stuff Here

IsExecuted = true;
}

.
.
.

//Inside your event or method

{
await MethodA();

while (!isExecuted) Thread.Sleep(200); // <-------

await MethodB();
}

-1

เพียงแค่ใส่ Wait () เพื่อรอจนกว่างานจะเสร็จ

GetInputReportViaInterruptTransfer().Wait();


สิ่งนี้จะบล็อกเธรดปัจจุบัน ดังนั้นนี่เป็นสิ่งที่ไม่ดีที่ควรทำ
Pure.Krome

-5

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

public async Task AnAsyncMethodThatCompletes()
{
    await SomeAsyncMethod();
    DoSomeMoreStuff();
    await Task.Factory.StartNew(() => { }); // <-- This line here, at the end
}

await AnAsyncMethodThatCompletes();
Console.WriteLine("AnAsyncMethodThatCompletes() completed.")

ผู้ลงคะแนนเสียงโปรดอธิบายอย่างที่ฉันถามในคำตอบ? เพราะมันใช้งานได้ดีเท่าที่ฉันรู้ ...
Jerther

3
ปัญหาคือวิธีเดียวที่คุณสามารถทำได้await+ Console.WriteLineคือการที่มันกลายเป็น a Taskซึ่งเป็นการควบคุมระหว่างทั้งสอง ดังนั้น 'การแก้ปัญหา' ของคุณจะให้ผลในที่สุดTask<T>ซึ่งไม่ได้แก้ไขปัญหา การทำTask.Waitพินัยกรรมจะหยุดการประมวลผลจริง ๆ (ด้วยความเป็นไปได้ของการหยุดชะงัก) ในคำอื่น ๆawaitไม่จริงรอมันก็รวมสองส่วนปฏิบัติการถ่ายทอดสดเป็นหนึ่งTask(ที่ใครบางคนสามารถดูหรือรอให้)
Ruben Bartelink

-5

ที่จริงฉันพบว่ามีประโยชน์มากขึ้นสำหรับฟังก์ชั่นที่ส่งกลับ IAsyncAction

            var task = asyncFunction();
            while (task.Status == AsyncStatus.Completed) ;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.