2012-10-23 4 views
6

bearbeiten Ich nehme an, die richtige Art und Weise erwarten zwingt die Arbeiter asynchron mit einem Task.Run, wie dies aufzurufen:Async/erwartet für lang laufende API-Methoden mit progress/Abbruch


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

Habe etwas Licht von http://blogs.msdn.com/b/pfxteam/archive/2012/04/12/10293335.aspx bekommen.


dies sollte einfach sein, aber ich bin neu zu async/erwarten so mit mir zu tragen. Ich baue eine Klassenbibliothek auf, die eine API mit einigen lang andauernden Operationen ausstellt. In der Vergangenheit habe ich eine Background mit Fortschrittsberichte und Abbruch zu tun, wie in diesem vereinfachten Codefragment:


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

und der Client-Code war wie:


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

Nun möchte Ich mag an Nutzen Sie das neue asynchrone Muster. Also, zuerst einmal hier ist, wie ich eine einfache lang laufende Methode in meiner API schreiben würde; hier bin ich nur eine Datei Zeile für Zeile zu lesen, nur einen realen Prozess zu emulieren, wo ich ein Dateiformat mit einiger Verarbeitung konvertieren müssen:


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

Aus Gründen des dieses Beispiels sagen, das ist eine Methode einer DummyWorker-Klasse. Nun, hier ist mein Client-Code (ein WPF-Test app):


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

Die Implementierung für die IProgress Schnittstelle kommt von http://blog.stephencleary.com/2010/06/reporting-progress-from-tasks.html, so dass Sie auf diese URL verweisen. Wie auch immer, in diesem Benutzertest wird die Benutzeroberfläche effektiv blockiert und ich sehe keinen Fortschritt. Was wäre das ganze Bild für ein solches Szenario, mit Bezug auf den konsumierenden Code?

+0

Ja, 'Task.Run()' ist der Weg, dies zu tun. Oder möglicherweise 'Task.Factory.StartNew()' mit der Option 'LongRunning', wenn die Aktion wirklich lang ist. – svick

Antwort

9

Wie oben auf diesem Blogbeitrag erwähnt, sind die Informationen in diesem Beitrag veraltet. Sie sollten die neue API IProgress<T> in .NET 4.5 verwenden.

Wenn Sie I/O verwenden zu blockieren, dann Kernverfahren Blockierung machen:

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

und es dann in Task.Run wickeln, wenn Sie es nennen:

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

, könnte alternativ Sie Schreiben Sie Build um asynchrone APIs zu verwenden, und rufen Sie es dann direkt aus dem Ereignishandler auf, ohne es in Task.Run zu verpacken.

+0

Danke, ich habe meine Methode blockiert, indem ich den Async entfernt und den Rückgabetyp in void geändert habe. – Naftis

+0

Beachten Sie, dass Sie über die Build() -Methode nicht auf Formularbenutzeroberflächenelemente zugreifen können. –

+0

@LefertisE: Dafür ist der Fortschrittsreporter da. –