2013-03-01 28 views
77

Tôi đang viết một ứng dụng WinForms chuyển dữ liệu đến 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ể được tìm thấy here. Tóm lại, khi tôi cần phải ghi dữ liệu vào thiết bị, đây là đoạn code đó được gọi là:Làm cách nào để đợi phương thức không đồng bộ hoàn thành?

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 rơi ra khỏi vòng lặp thời gian, tôi cần phải đọ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 vì vậy 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ư thế này:

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

Đối với những gì nó có giá trị, tờ khai cho GetInputReportViaInterruptTransfer() trông như thế này:

internal async Task<int> GetInputReportViaInterruptTransfer() 

Thật không may, tôi không phải là rất quen thuộc với các 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 chờ đợi và điều đó đã cho tôi ấn tượng rằng lời gọi đến GetInputReportViaInterruptTransfer() bên trong RequestToGetInputReport() sẽ đợi (và có thể nó?) Nhưng nó không giống như lời gọi đến RequestToGetInputReport() chính nó đang chờ đợi bởi vì tôi dường như đang bước vào vòng lặp while gần như ngay lập tức?

Có ai có thể làm rõ hành vi mà tôi thấy không?

Trả lời

88

Tránh async void. Yêu cầu phương thức của bạn trả về Task thay vì void. Sau đó, bạn có thể await chúng.

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

Cảm ơn bạn, Stephen. – user685869

+0

Rất đẹp, cảm ơn bạn. Tôi đã gãi đầu về một vấn đề tương tự và sự khác biệt là thay đổi 'void' thành' Task' giống như bạn đã nói. – Jeremy

+5

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

128

Điều quan trọng nhất để biết về asyncawaitawaitkhông chờ đợi cho các cuộc gọi liên quan để hoàn thành. Những gì await làm là trả lại 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ập lịch tiếp tục để thực hiện phần còn lại của phương pháp async và sau đó để trả lại quyền kiểm soát cho người gọi . Khi hoạt động không đồng bộ hoàn thành, quá trình hoàn thành được lên lịch sẽ thực thi.

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 trên giá trị trả về một async phương pháp của (mà phải được loại Task hoặc Task<T>) bằng cách gọi một Wait phương pháp 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 mã này đoạn mã, CallGetFooAsyncAndWaitOnResultđồng bộ trình bao bọc xung quanh phương pháp không đồng bộ GetFooAsync. Tuy nhiên, mô hình này là để tránh cho hầu hết các phần vì nó sẽ chặn một thread thread toàn bộ thread trong suốt thời gian hoạt động không đồng bộ. Việc sử dụng không hiệu quả các cơ chế không đồng bộ khác nhau được API tiếp xúc với nỗ lực rất lớn để cung cấp chúng.

Câu trả lời tại "await" doesn't wait for the completion of call có một số chi tiết, 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 giữ async void.Các giải thích tốt khác cho lý do tại sao có thể được tìm thấy tại http://www.tonicodes.net/blog/why-you-should-almost-never-write-void-asynchronous-methods/http://www.jaylee.org/post/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.aspx.

+14

Tôi thấy hữu ích khi suy nghĩ (và nói) về 'await' như là một "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à * chủ đề *. Vì vậy, nó có ý nghĩa để nói về 'RequestToSendOutputReport'" chờ đợi "' RequestToGetInputReport' mặc dù nó không phải là một * chặn * chờ đợi. –

+0

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

+5

Điều này phải là câu trả lời được chấp nhận, vì câu trả lời rõ ràng hơn cho câu hỏi thực tế (tức là cách chặn luồng theo cách không đồng bộ). – csvan

-6

Đoạn mã sau cho thấy cách đảm bảo phương thức được chờ đợi hoàn thành trước khi quay lại người gọi. BAO GIỜ, 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 các giải thích nếu bạn nghĩ khác đi.

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

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

Người bỏ phiếu, 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ư xa như tôi biết ... – Jerther

+2

Vấn đề là cách duy nhất bạn có thể thực hiện 'await' +' Console.WriteLine' là bởi 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 'Task ', không giải quyết được vấn đề. Làm một ['Task.Wait' sẽ] (http://stackoverflow.com/questions/32075084/manage-update-in-application-exit/32075919#) sẽ thực sự dừng xử lý (với khả năng bế tắc, vv). Nói cách khác, 'await' không thực sự chờ đợi, nó chỉ kết hợp hai phần thực thi không đồng bộ vào một 'Task' duy nhất (mà ai đó có thể xem hoặc chờ) –

18

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

var result = Task.Run(async() => { return await yourAsyncMethod(); }).Result; 
+1

Hoặc điều này cho" async "không đồng bộ của bạn: \t Task.Run (async() => {await yourAsyncMethod();}). Đợi(); –

Các vấn đề liên quan