2012-10-23 33 views
6

Sửa Tôi cho rằng cách thích hợp buộc chờ đợi để gọi người lao động không đồng bộ là với một Task.Run, như thế này:Async/chờ đợi cho các phương pháp API dài chạy với tiến độ/hủy


await Task.Run(() => builder.Build(dlg.FileName, cts.Token, new Progress(ReportProgress))); 

Có một số ánh sáng từ http://blogs.msdn.com/b/pfxteam/archive/2012/04/12/10293335.aspx.


điều này sẽ dễ dàng nhưng tôi mới không đồng bộ/đang chờ đợi với tôi. Tôi đang xây dựng một thư viện lớp để lộ một API với một số hoạt động dài hạn. Trong quá khứ, tôi đã sử dụng một BackgroundWorker để đối phó với báo cáo tiến độ và hủy, như trong đoạn mã này đơn giản:


public void DoSomething(object sender, DoWorkEventArgs e) 
{ 
      BackgroundWorker bw = (BackgroundWorker)sender; 
      // e.Argument is any object as passed by consumer via RunWorkerAsync... 

      do 
      { 
       // ... do something ... 

       // abort if requested 
       if (bw.CancellationPending) 
       { 
        e.Cancel = true; 
        break; 
       } //eif 

       // notify progress 
       bw.ReportProgress(nPercent); 
      } 
} 

và mã khách hàng giống như:


BackgroundWorker worker = new BackgroundWorker 
{ WorkerReportsProgress = true, 
    WorkerSupportsCancellation = true 
}; 
worker.DoWork += new DoWorkEventHandler(_myWorkerClass.DoSomething); 
worker.ProgressChanged += WorkerProgressChanged; 
worker.RunWorkerCompleted += WorkerCompleted; 
worker.RunWorkerAsync(someparam); 

Bây giờ tôi muốn tận dụng mẫu async mới. Vì vậy, trước hết ở đây là làm thế nào tôi muốn viết một phương pháp chạy dài đơn giản trong API của tôi; ở đây tôi chỉ đọc một dòng tập tin bằng cách dòng, chỉ để mô phỏng một quá trình thực thế giới mà tôi sẽ phải chuyển đổi một định dạng file với một số xử lý:


public async Task DoSomething(string sInputFileName, CancellationToken? cancel, IProgress progress) 
{ 
    using (StreamReader reader = new StreamReader(sInputFileName)) 
    { 
     int nLine = 0; 
     int nTotalLines = CountLines(sInputFileName); 

     while ((sLine = reader.ReadLine()) != null) 
     { 
      nLine++; 
      // do something here... 
     if ((cancel.HasValue) && (cancel.Value.IsCancellationRequested)) break; 
     if (progress != null) progress.Report(nLine * 100/nTotalLines); 
     } 
     return nLine; 
    } 
} 

Vì lợi ích của mẫu này, nói điều này là một phương thức của lớp DummyWorker. Bây giờ, đây là mã của tôi client (một ứng dụng thử nghiệm WPF):


private void ReportProgress(int n) 
{ 
    Dispatcher.BeginInvoke((Action)(() => { _progress.Value = n; })); 
} 

private async void OnDoSomethingClick(object sender, RoutedEventArgs e) 
{ 
    OpenFileDialog dlg = new OpenFileDialog { Filter = "Text Files (*.txt)|*.txt" }; 
    if (dlg.ShowDialog() == false) return; 

     // show the job progress UI... 

    CancellationTokenSource cts = new CancellationTokenSource(); 
    DummyWorker worker = new DummyWorker(); 
    await builder.Build(dlg.FileName, cts.Token, new Progress(ReportProgress)); 

    // hide the progress UI... 
} 

Việc thực hiện cho giao diện IProgress đến từ http://blog.stephencleary.com/2010/06/reporting-progress-from-tasks.html, vì vậy bạn có thể tham khảo URL đó. Dù sao, trong thử nghiệm sử dụng này giao diện người dùng bị chặn hiệu quả và tôi không thấy tiến bộ. Vì vậy, những gì sẽ là hình ảnh đầy đủ cho một kịch bản như vậy, với tham chiếu đến mã tiêu thụ?

+0

Vâng, 'Task.Run()' là cách để thực hiện điều đó. Hoặc có thể 'Task.Factory.StartNew()' với tập hợp tùy chọn 'LongRunning', nếu hành động thực sự là dài. – svick

Trả lời

9

Như đã lưu ý ở đầu bài đăng trên blog đó, thông tin trong bài đăng đó đã lỗi thời. Bạn nên sử dụng API IProgress<T> mới được cung cấp trong .NET 4.5.

Nếu bạn đang sử dụng blocking I/O, sau đó làm cốt lõi phương pháp ngăn chặn của bạn:

public void Build(string sInputFileName, CancellationToken cancel, IProgress<int> progress) 
{ 
    using (StreamReader reader = new StreamReader(sInputFileName)) 
    { 
    int nLine = 0; 
    int nTotalLines = CountLines(sInputFileName); 

    while ((sLine = reader.ReadLine()) != null) 
    { 
     nLine++; 
     // do something here... 
     cancel.ThrowIfCancellationRequested(); 
     if (progress != null) progress.Report(nLine * 100/nTotalLines); 
    } 

    return nLine; 
    } 
} 

và sau đó quấn nó trong Task.Run khi bạn gọi nó là:

private async void OnDoSomethingClick(object sender, RoutedEventArgs e) 
{ 
    OpenFileDialog dlg = new OpenFileDialog { Filter = "Text Files (*.txt)|*.txt" }; 
    if (dlg.ShowDialog() == false) return; 

    // show the job progress UI... 

    CancellationTokenSource cts = new CancellationTokenSource(); 
    DummyWorker worker = new DummyWorker(); 
    var progress = new Progress<int>((_, value) => { _progress.Value = value; }); 
    await Task.Run(() => builder.Build(dlg.FileName, cts.Token, progress); 

    // hide the progress UI... 
} 

Ngoài ra, bạn có thể viết lại Build để sử dụng các API không đồng bộ và sau đó chỉ cần gọi nó trực tiếp từ trình xử lý sự kiện mà không cần gói nó trong Task.Run.

+0

Cảm ơn, tôi đã chặn phương thức của mình bằng cách xóa bỏ async và thay đổi kiểu trả về thành void. – Naftis

+0

Lưu ý rằng bạn không được phép truy cập bất kỳ phần tử giao diện người dùng biểu mẫu nào từ phương thức Build(). –

+0

@LefterisE: Đó là những gì các phóng viên tiến bộ. –

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