2016-03-31 20 views
4

Ich versuche eine async await zu verwenden, um eine Fortschrittsanzeige auf meinem WinForm basierend auf einem Kopiervorgang zu aktualisieren, aber der Fortschrittsbalken wird nur aktualisiert, wenn die Copy Funktion beendet ist, und löst dann eine Ausnahme aus nicht aktualisieren, da es nicht im selben Thread ist?Async Progress Bar Update

Die Kopierfunktion muss nicht mit der Benutzeroberfläche interagieren, die Progress-Funktion jedoch.

Die Benutzeroberfläche ist jedoch nicht blockiert, daher scheint der asynchrone Teil wie erwartet zu funktionieren, er interagiert nur mit dem UI-Thread, der nicht funktioniert.

long fileProgress = 0; 
long totalProgress = 0; 
bool complete = false; 

CopyFileEx.CopyFileCallbackAction callback(FileInfo source, FileInfo destination, object state, long totalFileSize, long totalBytesTransferred) 
{ 
     fileProgress = totalBytesTransferred; 
     totalProgress = totalFileSize; 
     return CopyFileEx.CopyFileCallbackAction.Continue; 
} 

async Task Progress() 
{ 
     await Task.Run(() => 
     { 
      while (!complete) 
      { 
       if (fileProgress != 0 && totalProgress != 0) 
       { 
        fileProgressBar.Value = (int)(fileProgress/totalProgress) * 100; 
       } 
      } 
     }); 
} 

private async void startButton_Click(object sender, EventArgs e) 
{ 
     Copy(); 
     await Progress(); 
     MessageBox.Show("Done"); 
} 

void Copy() 
{ 
     Task.Run(() => 
     { 
      CopyFileEx.FileRoutines.CopyFile(new FileInfo(@"C:\_USB\Fear.rar"), new FileInfo(@"H:\Fear.rar"), CopyFileEx.CopyFileOptions.All, callback, null); 
      complete = true; 
     }); 
} 

Antwort

1
  1. async/await ist aussehen alles darüber, einen Thread nicht zu blockieren - irgendeinen Thread - whe n Umgang mit I/O. Setzen Sie eine Blockierung I/O Anruf innerhalb Task.Run() (wie Sie in Copy() getan) vermeidet nicht blockieren - es nur eine Aufgabe erstellen, die ein anderer Thread später abholen wird, nur um es selbst zu finden wird blockiert, wenn es die Blockierung trifft CopyFileEx.FileRoutines.CopyFile() Methode.
  2. Sie erhalten diesen Fehler, weil Sie async/await nicht ordnungsgemäß verwenden (unabhängig von den oben genannten). Denken Sie darüber nach, welcher Thread versucht, das UI-Objekt fileProgressBar zu ändern: der zufällige threadpool Thread, der die Aufgabe übernimmt, die Sie auf Task.Run() erstellen, führt fileProgressBar.Value = ... aus, die offensichtlich werfen wird.

Dies ist eine Möglichkeit, diese Situation zu vermeiden:

async Task Progress() 
{ 
     await Task.Run(() => 
     { 
      //A random threadpool thread executes the following: 
      while (!complete) 
      { 
       if (fileProgress != 0 && totalProgress != 0) 
       { 
        //Here you signal the UI thread to execute the action: 
        fileProgressBar.Invoke(() => 
        { 
         //This is done by the UI thread: 
         fileProgressBar.Value = (int)(fileProgress/totalProgress) * 100 
        }); 
       } 
      } 
     }); 
} 

private async void startButton_Click(object sender, EventArgs e) 
{ 
     await Copy(); 
     await Progress(); 
     MessageBox.Show("Done"); //here we're on the UI thread. 
} 

async Task Copy() 
{ 
    //You need find an async API for file copy, and System.IO has a lot to offer. 
    //Also, there is no reason to create a Task for MyAsyncFileCopyMethod - the UI 
    // will not wait (blocked) for the operation to complete if you use await: 
    await MyAsyncFileCopyMethod(); 
    complete = true; 
} 
+0

Danke, das hat gut funktioniert. –

+0

Mit dem Hinweis 'fileProgressBar.Invoke (() => {...});' wird nicht kompiliert, Sie müssen einen Delegattyp verwenden wie: 'fileProgressBar.Invoke (new Action (() => {. ..})); ' – Softerware

3

Wenn Asynchron mit/erwarte ich die IProgress und Fortschritt-Implementierungen verwenden, einige der Callback-Details, die abstrahieren.

Ich bin ziemlich sicher, was Sie dort haben, funktioniert nicht, weil es in einem Hintergrund-Thread über den Aufruf Task.Run() ausgeführt wird, so dass es nicht wirklich auf die UI-Steuerelemente zugreifen kann.

In diesem Artikel über die Berichterstattung Fortschritte mit async/erwarten, ich denke, es wird helfen.

http://blog.stephencleary.com/2012/02/reporting-progress-from-async-tasks.html

In Ihrer aktuellen Implementierung, wenn Sie will es mit dem Rückruf arbeiten ich glaube, ich würde nur die Fortschrittsleiste direkt in Ihrer Callback-Methode aktualisieren statt Prüfstatus des Fortschritts Variablen in einer Schleife, die ist Gehen Sie dazu, Ihre Benutzeroberfläche zu blockieren, wenn Sie sie aus dem Hintergrundthread entfernen, um tatsächlich auf die Statusleiste zuzugreifen.

2

Sie müssen hier IProgress<T> verwenden:

private async void startButton_Click(object sender, EventArgs e) 
{ 
     var progress = new Progress<int>(percent => 
     { 
     fileProgressBar.Value = percent; 
     }); 

     await Copy(progress); 

     MessageBox.Show("Done"); 
} 

void Copy(IProgress<int> progress) 
{ 
     Task.Run(() => 
     { 
      CopyFileEx.FileRoutines.CopyFile(new FileInfo(@"C:\_USB\Fear.rar"), new FileInfo(@"H:\Fear.rar"), CopyFileEx.CopyFileOptions.All, callback, null,progress); 
      complete = true; 
     }); 
} 

und Ihre Callback-Methode den Fortschritt IProgress<T> wie mitteilen:

CopyFileEx.CopyFileCallbackAction callback(FileInfo source, FileInfo destination, object state, long totalFileSize, long totalBytesTransferred,IProgress<int> progress) 
{ 
     fileProgress = totalBytesTransferred; 
     totalProgress = totalFileSize; 
     progress.Report(Convert.ToInt32(fileProgress/totalProgress)); 
     return CopyFileEx.CopyFileCallbackAction.Continue; 
} 

Sie können this very good article by Stepehen Cleary