2013-03-14 42 views
24

Tôi hoàn toàn mới với C# 5 mới async/await từ khóa và tôi quan tâm đến cách tốt nhất để triển khai sự kiện tiến trình.C# async/await Progress event trên Task <> object

Bây giờ tôi sẽ thích nếu sự kiện Progress ở trên chính bản thân số Task<>. Tôi biết tôi chỉ có thể đặt các sự kiện trong lớp có chứa các phương pháp không đồng bộ và vượt qua một số loại đối tượng nhà nước trong xử lý sự kiện, nhưng với tôi mà dường như nhiều hơn một workaround hơn một giải pháp. Tôi cũng có thể muốn các nhiệm vụ khác nhau để kích hoạt các trình xử lý sự kiện trong các đối tượng khác nhau, điều này nghe có vẻ lộn xộn theo cách này.

Có cách nào tôi có thể làm điều gì đó tương tự như sau ?:

var task = scanner.PerformScanAsync(); 
task.ProgressUpdate += scanner_ProgressUpdate; 
return await task; 

Trả lời

32

một cách đơn giản, Taskkhông hỗ trợ tiến bộ. Tuy nhiên, đã có cách làm thông thường, sử dụng giao diện IProgress<T>. Các Task-based Asynchronous Pattern về cơ bản cho thấy quá tải các phương pháp async của bạn (nơi nó có ý nghĩa) để cho phép khách hàng để vượt qua trong một thực hiện IProgess<T>. Phương pháp async của bạn sau đó sẽ báo cáo tiến độ qua đó.

Windows Runtime (WinRT) API không có chỉ số tiến độ xây dựng-in, trong IAsyncOperationWithProgress<TResult, TProgress>IAsyncActionWithProgress<TProgress> loại ... vì vậy nếu bạn đang thực sự viết cho WinRT, đó là giá trị xem xét - nhưng đọc các ý kiến dưới đây là tốt.

+2

Đối với WinRT, sẽ dễ dàng hơn khi viết phương thức 'async' bình thường với' IProgress '(theo các quy ước C#) và sau đó sử dụng 'AsyncInfo.Run' để bọc nó vào' IAsyncOperationWithProgress'/'IAsyncActionWithProgress' hơn là triển khai trực tiếp các giao diện đó. –

+0

@StephenCleary: Vâng, nó phụ thuộc vào việc bạn đang suy nghĩ về * người gọi * hay * triển khai *. Nếu bạn đang cố gắng trưng ra các chức năng để người khác gọi vào, tôi đã nghĩ các kiểu "bản địa" của WinRT sẽ thích hợp hơn. Rõ ràng là có nhiều bộ chuyển đổi khác nhau giữa hai loại. –

+0

Việc theo các quy ước C# trong C# dễ dàng hơn và sử dụng các trình bao bọc ('AsTask' /' AsyncInfo') tại các ranh giới. Vì vậy, một thành phần WinRT được thực hiện trong C# sẽ trả về kiểu giao diện WinRT, nhưng sẽ thực hiện nó bằng cách sử dụng thường xuyên 'async' được bao bọc trong 'AsyncInfo'. –

37

Cách tiếp cận đề nghị được mô tả trong tài liệu hướng dẫn Task-based Asynchronous Pattern, mang đến cho mỗi phương pháp không đồng bộ riêng của mình IProgress<T>:

public async Task PerformScanAsync(IProgress<MyScanProgress> progress) 
{ 
    ... 
    if (progress != null) 
    progress.Report(new MyScanProgress(...)); 
} 

Cách sử dụng:

var progress = new Progress<MyScanProgress>(); 
progress.ProgressChanged += ... 
PerformScanAsync(progress); 

Ghi chú:

  1. Theo quy ước, tham số progress có thể là null nếu người gọi không cần báo cáo tiến độ, vì vậy hãy đảm bảo kiểm tra điều này trong phương thức async của bạn.
  2. Báo cáo tiến trình tự nó không đồng bộ, vì vậy bạn nên tạo một phiên bản đối số mới mỗi khi bạn gọi (thậm chí tốt hơn, chỉ cần sử dụng các loại bất biến cho arg sự kiện của bạn). Bạn nên không biến đổi và sau đó sử dụng lại cùng một đối tượng đối số cho nhiều cuộc gọi đến Progress.
  3. Loại Progress<T> sẽ nắm bắt ngữ cảnh hiện tại (ví dụ: ngữ cảnh giao diện người dùng) khi xây dựng và sẽ tăng sự kiện ProgressChanged trong ngữ cảnh đó. Vì vậy, bạn không phải lo lắng về việc sắp xếp lại chuỗi giao diện người dùng trước khi gọi số Report.
+0

Vì vậy, tôi hiểu và có lẽ để giúp đỡ người khác trong tương lai ... lý do của bạn cho điểm 2. là nếu thay đổi được thực hiện cho đối tượng có thể thay đổi Progress.Report() trước khi phương thức Report hoàn thành thì nó sẽ dẫn đến các giá trị ban đầu không được báo cáo? –

+3

Hầu hết. Ý tôi là khi phương thức 'Báo cáo' hoàn tất (và trả về), báo cáo thực tế vẫn chưa được thực hiện. Nếu 'T' của bạn có thể thay đổi và bạn thay đổi nó sau khi chuyển nó sang' Báo cáo', thì bạn có một điều kiện chủng tộc. Báo cáo thực tế có thể thấy giá trị cũ, giá trị mới hoặc hỗn hợp (nghĩa là giá trị cũ cho một số trường và giá trị mới cho các trường khác). –

+0

Có ý nghĩa hơn bây giờ cảm ơn bạn. Tôi đọc một vài blog của bạn, vv, nơi bạn đã đề cập đến điều kiện chủng tộc nhưng nó không rõ ràng cho đến khi tôi đọc câu hỏi này. Cảm ơn –

4

Tôi đã chia sẻ câu trả lời này từ một số bài đăng vì tôi đang cố gắng tìm ra cách làm công việc này cho mã ít tầm thường hơn (tức là sự kiện thông báo thay đổi).

Giả sử bạn có một bộ xử lý mục đồng bộ sẽ thông báo số mục mà sắp bắt đầu hoạt động. Đối với ví dụ của tôi, tôi chỉ cần thao tác nội dung của nút Process, nhưng bạn có thể dễ dàng cập nhật một thanh tiến trình, v.v.

private async void BtnProcess_Click(object sender, RoutedEventArgs e) 
{  
    BtnProcess.IsEnabled = false; //prevent successive clicks 
    var p = new Progress<int>(); 
    p.ProgressChanged += (senderOfProgressChanged, nextItem) => 
        { BtnProcess.Content = "Processing page " + nextItem; }; 

    var result = await Task.Run(() => 
    { 
     var processor = new SynchronousProcessor(); 

     processor.ItemProcessed += (senderOfItemProcessed , e1) => 
           ((IProgress<int>) p).Report(e1.NextItem); 

     var done = processor.WorkItWorkItRealGood(); 

     return done ; 
    }); 

    BtnProcess.IsEnabled = true; 
    BtnProcess.Content = "Process"; 
} 

Phần chìa khóa để điều này được đóng trong biến Progress<> bên ItemProcessed thuê bao. Điều này cho phép mọi thứ đến Just works ™.

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