2012-10-23 31 views
6

編輯 我想強迫的正確方法是等待調用工人異步是一個Task.Run,​​像這樣:異步/等待與進度/取消長時間運行的API方法


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

http://blogs.msdn.com/b/pfxteam/archive/2012/04/12/10293335.aspx得到了一些光。


這應該很容易,但我是新來的異步/等待,所以忍受着我。我正在構建一個暴露API的類庫,其中包含一些長時間運行的操作。在過去,我用一個BackgroundWorker處理進度在這個簡化的代碼片段報告和取消,如:


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

和客戶端代碼像:


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

現在,我想利用新的異步模式。所以,首先這裏是我如何在我的API中編寫一個簡單的長時間運行的方法;在這裏我只是逐行讀取文件中的行,只是爲了模擬真實世界的過程中,我將有一個轉換文件格式有一些處理:


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

對於此示例的緣故,說這是DummyWorker類的一種方法。現在,這裏是我的客戶代碼(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... 
} 

爲IProgress接口的實現來自http://blog.stephencleary.com/2010/06/reporting-progress-from-tasks.html,所以你可以參考該URL。無論如何,在這個使用測試中,UI被有效阻擋,我看不到任何進展。那麼參考消費代碼,對於這種情況,完整的畫面會是什麼?

+0

是的,'Task.Run()'是做到這一點的方法。或者如果動作確實很長,可能會設置''Task.Factory.StartNew()'設置'LongRunning'選項。 – svick

回答

9

正如該博客頂部所述,該帖子中的信息已過時。您應該使用.NET 4.5中提供的新的IProgress<T> API。

如果您使用的是阻塞I/O,然後讓你的核心方法阻止:

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

,然後把它包在Task.Run,當你把它叫做:

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

或者,你可以重寫Build以使用異步API,然後直接從事件處理程序調用它,而不將其包裝在Task.Run中。

+0

謝謝,我通過刪除異步並將返回類型更改爲void來阻止我的方法。 – Naftis

+0

請注意,您不能從Build()方法訪問任何表單UI元素。 –

+0

@LefterisE:這就是進步記者的用意。 –