Làm thế nào để chờ phương thức async hoàn thành?


138

Tôi đang viết một ứng dụng WinForms để truyền dữ liệu sang thiết bị lớp USB HID. Ứng dụng của tôi sử dụng thư viện Generic HID tuyệt vời v6.0 có thể tìm thấy ở đây . Tóm lại, khi tôi cần ghi dữ liệu vào thiết bị, đây là mã được gọi:

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

Khi mã của tôi thoát khỏi vòng lặp while, tôi cần đọc một số dữ liệu từ thiết bị. Tuy nhiên, thiết bị không thể phản hồi ngay lập tức nên tôi cần đợi cuộc gọi này trở lại trước khi tiếp tục. Vì nó hiện đang tồn tại, RequestToGetInputReport () được khai báo như sau:

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

Đối với giá trị của nó, khai báo cho GetInputReportViaInterruptTransfer () trông như thế này:

internal async Task<int> GetInputReportViaInterruptTransfer()

Thật không may, tôi không quen lắm với hoạt động của các công nghệ async / await mới trong .NET 4.5. Tôi đã đọc một chút trước đó về từ khóa đang chờ và điều đó mang lại cho tôi cảm giác rằng cuộc gọi đến GetInputReportViaInterruptTransfer () bên trong RequestToGetInputReport () sẽ chờ (và có thể nó giống như vậy?) Nhưng dường như đó không phải là cuộc gọi đến RequestToGetInputReport () Bản thân nó đang chờ đợi vì dường như tôi đang quay lại vòng lặp while gần như ngay lập tức?

Bất cứ ai có thể làm rõ hành vi mà tôi đang nhìn thấy?

Câu trả lời:


131

Tránh async void. Có phương pháp của bạn trở lại Taskthay vì void. Sau đó, bạn có thể awaithọ.

Như thế này:

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
Rất tuyệt cảm ơn bạn. Tôi đã gãi đầu của tôi về một vấn đề tương tự và sự khác biệt là thay đổi voidđể Taskgiống như bạn đã nói.
Jeremy

8
Đó là một điều nhỏ, nhưng để tuân theo quy ước, cả hai phương thức nên thêm Async vào tên của chúng, ví dụ: RequestToGetInputReportAsync ()
tymtam

6
và nếu người gọi là chức năng chính thì sao?
cộng sinh

14
@symbiont: Sau đó sử dụngGetAwaiter().GetResult()
Stephen Cleary

4
@AhmedSalah Biểu Taskthị việc thực thi phương thức - vì vậy returncác giá trị được đặt trên Task.Resultvà các ngoại lệ được đặt trên Task.Exception. Với void, trình biên dịch không có nơi nào để đặt ngoại lệ, vì vậy chúng chỉ được nâng lên trên một chuỗi nhóm luồng.
Stephen Cleary

229

Điều quan trọng nhất cần biết asyncawaitđó là await không chờ cuộc gọi liên quan hoàn tất. Điều gì awaitlàm là trả về kết quả của hoạt động ngay lập tức và đồng bộ nếu hoạt động đã hoàn thành hoặc, nếu không, để lên lịch tiếp tục thực hiện phần còn lại của asyncphương thức và sau đó trả lại quyền điều khiển cho người gọi. Khi hoạt động không đồng bộ hoàn thành, việc hoàn thành theo lịch trình sẽ thực hiện.

Câu trả lời cho câu hỏi cụ thể trong tiêu đề của câu hỏi của bạn là chặn asyncgiá trị trả về của phương thức (phải là loại Taskhoặc Task<T>) bằng cách gọi một Waitphương thức thích hợp :

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

Trong đoạn mã này, CallGetFooAsyncAndWaitOnResultlà một trình bao bọc đồng bộ xung quanh phương thức không đồng bộ GetFooAsync. Tuy nhiên, phần lớn mẫu này nên được tránh vì phần lớn nó sẽ chặn toàn bộ chuỗi nhóm luồng trong suốt thời gian của hoạt động không đồng bộ. Đây là việc sử dụng không hiệu quả các cơ chế không đồng bộ khác nhau được các API thể hiện nhằm nỗ lực rất lớn để cung cấp chúng.

Câu trả lời tại "await" không chờ đợi việc hoàn thành cuộc gọi có một số giải thích chi tiết hơn về các từ khóa này.

Trong khi đó, hướng dẫn của @Stephen Cleary về việc async voidgiữ. Những lời giải thích hay khác về lý do tại sao có thể được tìm thấy tại http://www.tonicodes.net/blog/why-you-should-al most-never-write-void-asynyncous-methods /https://jaylee.org/archive/ 2012/07/08 / c-sharp-async-tips-and-trick-part-2-async-void.html


18
Tôi thấy hữu ích khi nghĩ (và nói) về await"chờ đợi không đồng bộ" - nghĩa là, nó chặn phương thức (nếu cần) nhưng không phải là luồng . Vì vậy, thật hợp lý khi nói về RequestToSendOutputReport"chờ đợi" RequestToGetInputReportmặc dù đó không phải là chờ đợi chặn .
Stephen Cleary

@Richard Cook - cảm ơn bạn rất nhiều vì đã giải thích thêm!
bmt22033

10
Đây phải là câu trả lời được chấp nhận, vì nó trả lời rõ ràng hơn câu hỏi thực tế (tức là làm thế nào để chặn chuỗi thông minh trên một phương thức không đồng bộ).
csvan 18/2/2015

giải pháp tốt nhất là chờ async cho đến khi hoàn thành nhiệm vụ là var result = Task.Run (async () => {return await yourMethod ();}). result;
Ram chittala

70

Giải pháp tốt nhất để chờ AsynMethod hoàn thành nhiệm vụ là

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

15
Hoặc cái này cho async "void" của bạn: Task.Run (async () => {await yourAsyncMethod ();}). Wait ();
Jiří Herník

1
Lợi ích của việc này so với kết quảAsyncMethod () của bạn là gì?
Justin J Stark

1
Chỉ cần truy cập vào thuộc tính .Result không thực sự đợi cho đến khi tác vụ hoàn tất. Trong thực tế, tôi tin rằng nó ném một ngoại lệ nếu nó được gọi trước khi một nhiệm vụ được hoàn thành. Tôi nghĩ rằng lợi thế của việc gói này trong lệnh gọi Task.Run () là, như Richard Cook đề cập bên dưới, "await" không thực sự chờ đợi một nhiệm vụ hoàn thành, nhưng sử dụng lệnh gọi .Wait () sẽ chặn toàn bộ nhóm luồng của bạn . Điều này cho phép bạn (đồng bộ) chạy một phương thức async trên một luồng riêng biệt. Hơi khó hiểu, nhưng nó có.
Lucas Leblanc

kết quả tốt đẹp ở đó, đúng thứ tôi cần
Gerry

Nhắc nhở nhanh ECMA7 featurelike assync () hoặc chờ đợi sẽ không hoạt động trong môi trường tiền ECMA7.
Mbotet

0

Đây là một cách giải quyết bằng cách sử dụng cờ:

//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

chỉ cần đặt Wait () để đợi cho đến khi hoàn thành nhiệm vụ

GetInputReportViaInterruptTransfer().Wait();


Điều này chặn các chủ đề hiện tại. Vì vậy, đây thường là một điều xấu để làm.
Pure.Krom

-4

Trên thực tế tôi thấy điều này hữu ích hơn cho các hàm trả về IAsyncAction.

            var task = asyncFunction();
            while (task.Status == AsyncStatus.Completed) ;

-5

Đoạn mã sau đây cho thấy một cách để đảm bảo phương thức được chờ hoàn thành trước khi quay lại với người gọi. TUY NHIÊN, tôi sẽ không nói đó là thực hành tốt. Vui lòng chỉnh sửa câu trả lời của tôi với lời giải thích nếu bạn nghĩ khác.

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

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

Downvoters, quan tâm để giải thích, như tôi đã hỏi trong câu trả lời? Bởi vì điều này hoạt động tốt như tôi biết ...
Jerther 10/03/2016

3
Vấn đề là cách duy nhất bạn có thể thực hiện await+ Console.WriteLinelà bằng cách nó trở thành một Task, từ bỏ quyền kiểm soát giữa hai. do đó, "giải pháp" của bạn cuối cùng sẽ mang lại một Task<T>, không giải quyết được vấn đề. Làm theo Task.Waitý muốn sẽ thực sự dừng xử lý (với khả năng bế tắc, v.v.). Nói cách khác, awaitkhông thực sự chờ đợi, nó chỉ đơn giản là kết hợp hai phần thực thi không đồng bộ thành một phần duy nhất Task(mà ai đó có thể xem hoặc chờ)
Ruben Bartelink
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.