2017-12-17 8 views
-1

Dies ist nur Beispiel Testcode, den ich versuche zu arbeiten. Ich weiß, dass es bessere Möglichkeiten gibt, Fortschritte zu melden, aber ich möchte nur wissen, warum dieser Code nicht funktioniert. Ich lese eine Datei und berichte über den Fortschritt, aber sie friert die Benutzeroberfläche ein und wartet, bis die gesamte Aufgabe erledigt ist.Wie Implementieren paralleler Task in WPF?

private async void runtTasksButton_Click(object sender, RoutedEventArgs e) 
{ 
    List<Task> tasks = new List<Task>(); 
    var t = Task.Factory.StartNew(() => 
     { 
      FileStream fs = File.OpenRead(@"C:\Whatever.txt"); 
      var totalLength = fs.Length; 

      this.Dispatcher.BeginInvoke(new Action(() => 
      { 
       progressBar1.Maximum = totalLength; 
      }), null); 

      using (fs) 
      { 
       byte[] b = new byte[1024]; 

       while (fs.Read(b, 0, b.Length) > 0) 
       { 
        this.Dispatcher.BeginInvoke(new Action(() => 
        { 
         progressBar1.Value += 1024; 
        }), null); 
       }; 
      } 
     }); 
    tasks.Add(t); 
    await Task.WhenAll(tasks.ToArray()); 
} 

EDIT: Ich weiß über BackgroundWorker und async/await. Aber ich versuche nur, diesen Block Code herauszufinden, um ein tieferes Verständnis der TPL zu bekommen. Ich habe die FileStream.ReadAsync() Methode und konnte die ProgressBar zur Laufzeit als solche aktualisieren:

private void runtTasksButton_Click(object sender, RoutedEventArgs e) 
{ 
    progressTextBox.Text = ""; 
    label1.Content = "Milliseconds: "; 
    progressBar1.Value = 0; 
    progressBar2.Value = 0; 

    var watch = Stopwatch.StartNew(); 
    List<Task> tasks = new List<Task>(); 
    var t1 = Task.Factory.StartNew(() => ReadFile(@"C:\BigFile1.txt", progressBar1)); 
    var t2 = Task.Factory.StartNew(() => ReadFile(@"C:\BigFile2.txt", progressBar2)); 

    tasks.Add(t1); 
    tasks.Add(t2); 

    Task.Factory.ContinueWhenAll(tasks.ToArray(), 
     result => 
     { 
      var time = watch.ElapsedMilliseconds; 
      this.Dispatcher.BeginInvoke(new Action(() => 
       label1.Content += time.ToString())); 
     }); 
} 

private async Task ReadFile(string path, ProgressBar progressBar) 
{ 
    FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None, 8192, true); 
    var totalLength = fs.Length; 

    await this.Dispatcher.BeginInvoke(new Action(() => 
    { 
     progressBar.Maximum = totalLength; 
    })); 

    using (fs) 
    { 
     byte[] b = new byte[8192]; 
     while (await fs.ReadAsync(b, 0, b.Length) > 0) 
     { 
      await this.Dispatcher.BeginInvoke(new Action(() => 
      { 
       progressBar.Value += 8192; 
      })); 
     }; 
    } 
} 

Doch jetzt geht die Steuerung in den ContinueWhenAll ohne die Datei zu warten Aufgaben Lesen abzuschließen. Die Tasks aktualisieren jedoch die entsprechenden ProgressBars wie vorgesehen. Wenn jemand einige Erklärungen geben könnte, würde das geschätzt werden.

+1

http: //www.wpf-tutorial.com/misc/multi-threading-with-the-backgroundworker/ – zaitsman

+0

Mögliches Duplikat von [Wie verwende ich WPF Background Worker] (https://stackoverflow.com/questions/5483565/how-to -use-wpf-background-worker) – zaitsman

+2

Wenn Sie 'async/await' verwenden, ist dies korrekt e ist im Allgemeinen nicht erforderlich für "Dispatcher.BeginInvoke", da nach Abschluss der Task die Steuerung an den ursprünglichen Thread zurückgegeben wird, in diesem Fall an den UI-Thread. Das Problem hier ist, dass Sie eine andere CPU-gebundene 'Task' (' Task.Factory.StartNew') erstellen, die höchstwahrscheinlich ein Worker-Thread sein wird. Verwenden Sie keine CPU-gebundenen Aufgaben mit E/A-gebundenen Operationen, z. B. Lesen einer Datei. Verwenden Sie an erster Stelle die 'xxxAsync'-Versionen der Operationen. Keine Notwendigkeit für "Task.Run" oder gleichwertige – MickyD

Antwort

1

Dies ist eine klassische Falle mit Task.Factory.StartNew. Die Methode gibt Task<T> zurück, wobei T der Rückgabetyp Ihres Rückrufs ist. Ihr Rückruf ist ReadFile, der eine Aufgabe zurückgibt. Daher enden Sie mit einem Task<Task>, und Sie warten auf die äußere Aufgabe, während Sie auf die innere Aufgabe warten sollten.

Zwei Lösungen:

  1. Die empfohlene ein: Task.Run statt Task.Factory.StartNew verwenden. Das ist eine allgemeine Empfehlung, die Sie nie verwenden sollten, wenn Sie nicht wissen, dass es die richtige Lösung ist. Task.Run vermeidet viele Traps, wie die, in die Sie hineingeraten sind.

    var t1 = Task.Run(() => ReadFile(@"C:\BigFile1.txt", progressBar1)); 
    
  2. Wenn Sie jemals einen legitimen Grund Task.Factory.StartNew haben zu verwenden, dann können Sie die .Unwrap()-Methode für die Aufgabe verwenden zu können, auf dem inneren warten:

    var t1 = Task.Factory.StartNew(() => ReadFile(@"C:\BigFile1.txt", progressBar1)).Unwrap(); 
    
+0

Vielen Dank, das war genau das Thema! – dotnetzen

Verwandte Themen