2017-12-17 21 views
-1

これは私が動作させようとしている単なるサンプルテストコードです。私は進行状況を報告するより良い方法があることを知っていますが、私はこのコードがなぜ機能していないのかを知りたいだけです。私はファイルを読み込んで進行状況を報告していますが、UIをフリーズし、タスク全体が完了するまで待ちます。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()); 
} 

編集:私は約BackgroundWorkerasync/await知っています。しかし、私はちょうどTPLの深い理解を得るためにこのコードブロックを理解しようとしています。私はFileStream.ReadAsync()メソッドを使用していた、そのように実行時にProgressBarを更新することができました:

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

しかし、今で制御が完了するまでにタスクを読み込むファイルを待たずにContinueWhenAllに入ります。しかし、タスクは意図したとおりにそれぞれのプログレスバーを更新します。誰かが説明をすることができれば、それは高く評価されます。

+1

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

+0

[WPFバックグラウンドワーカーを使用する方法](https://stackoverflow.com/questions/5483565/how-to)の可能な複製-use-wpf-background-worker) – zaitsman

+2

'async/await'を正しく使用している場合は、タスクが完了すると、制御は元のスレッド(この場合はUIスレッド)に返されるため、通常、Dispatcher.BeginInvokeは必要ありません。ここで問題となるのは、別のCPUバウンドの 'Task'(' Task.Factory.StartNew')を作っていることです。これはワーカースレッドになる可能性が最も高いです。ファイル読み込みなどのI/Oバウンド操作でCPUバインドタスクを使用しないでください。最初の場所で操作の 'xxxAsync'バージョンを使用してください。 'Task.Run'やそれに相当する – MickyD

答えて

1

これは古典的なトラップで、Task.Factory.StartNewです。このメソッドはTask<T>を返します。Tがコールバックの戻り値の型です。コールバックはReadFileで、タスクを返します。したがって、あなたはTask<Task>になり、内部タスクを待つ間に外部タスクを待っています。

2種類の溶液:

  1. 推奨1:Task.Runの代わりTask.Factory.StartNewを使用しています。これは一般的な推奨事項です。適切な解決方法がわからない限り、Task.Factory.StartNewを使用しないでください。 Task.Runは、あなたが遭遇したような多くのトラップを回避します。

    var t1 = Task.Run(() => ReadFile(@"C:\BigFile1.txt", progressBar1)); 
    
  2. あなたは、あなたが内側のいずれかに待つことができるようにタスクに.Unwrap()メソッドを使用することができ、Task.Factory.StartNewを使用する正当な理由がある場合:

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

ありがとうございました、それはまさに問題でした! – dotnetzen

関連する問題