15

多くの初心者質問:なぜこのParallel.ForEachコードはプログラムをフリーズしますか?

このコードは、メインウィンドウのリストからいくつかのプロキシを取得します(変数はさまざまな機能間で利用可能にする方法を理解できませんでした)。 httpwebrequest)、それをfinishedProxiesというリストに追加します。

私はスタートボタンを押して何らかの理由で、プログラム全体がハングアップします。私はParallelがUIスレッドだけを残してアクションごとに別々のスレッドを作成し、応答性があるという印象を受けましたか?

private void start_Click(object sender, RoutedEventArgs e) 
     { 
      // Populate a list of proxies 
      List<string> proxies = new List<string>(); 
      List<string> finishedProxies = new List<string>(); 

      foreach (string proxy in proxiesList.Items) 
      { 
       proxies.Add(proxy); 
      } 

      Parallel.ForEach<string>(proxies, (i) => 
      { 
       string checkResult; 
       checkResult = checkProxy(i); 

       finishedProxies.Add(checkResult); 
       // update ui 
       /* 
       status.Dispatcher.Invoke(
        System.Windows.Threading.DispatcherPriority.Normal, 
        new Action(
        delegate() 
        { 
         status.Content = "hello" + checkResult; 
        } 
       )); */ 
       // update ui finished 


       //Console.WriteLine("[{0}] F({1}) = {2}", Thread.CurrentThread.Name, i, CalculateFibonacciNumber(i)); 
      }); 


     } 

私はParallel.Foreach内のUIに変更を加えるためにコメントアウトし、スタートボタンを押した後、それはプログラムの凍結を作るのコードを使用してみました。以前は私のために働いていましたが、私はスレッドクラスを使用しました。

Parallel.Foreach内からUIを更新するにはどうすればいいですか?Parallel.Foreachを動作させてUIをフリーズさせないようにするにはどうすればいいですか?あなたのコードで

Here's the whole code.

+2

あなたは呼び出し要求でUIスレッドを撃退していますが、通常の任務をやり遂げることはもうありません。 UIの再描画に似ています。少なくとも優先度をバックグラウンドに下げる。 –

+0

@dsp_099б「これは私のために働いたが、私はスレッドクラスを使った」という意味はどういう意味ですか? – Fulproof

答えて

15

UIスレッドで並列処理を開始しないでください。 this pageの「UIスレッドでの並列ループの実行を避ける」ヘッダーの例を参照してください。

更新:または、新しいスレッドマニュアルを作成して、その中で処理を開始することができます。それには何も問題はありません。

ジム・ミッシェル氏も指摘しているように、同時に複数のスレッドからリストにアクセスしているため、競合状態が存在します。 ListにはConcurrentBagを代入するか、またはアクセスするたびにlockステートメント内のリストを囲みます。

+4

'' List''の代わりに '' ConcurrentBag'の代わりにどういう意味ですか? ...あなたは本当に "ConcurrentBag"を "List"に置き換えたのですか? – Fulproof

+1

@Fulproof:私は、正しい英語は「* Bの代用*」==「* Bの代用*」と考えています。 – Jon

+1

[別のもの(A)を別のもの(B)に置き換える]」(http://dictionary.reverso.net/english-cobuild/to%20substitute%20a%20for%20b)==「AをBと置き換える」= =(A) "が起こり、他の"(B)の機能を実行する。あなたはあなたの用法を参考にしてもらえますか? – Fulproof

1

一つの問題は、あなたが同時に複数のスレッドからFinishedProxies.Addを呼んでいるということです。 List<T>はスレッドセーフではないため、問題が発生します。ロックやその他の同期プリミティブで保護するか、コンカレントコレクションを使用する必要があります。

これが原因でUIロックアップが発生するかどうかはわかりません。それ以上の情報がなければ、言うことは難しいです。 proxiesのリストが非常に長く、checkProxyの実行に時間がかからない場合、タスクはすべてInvokeコールの後ろに並びます。それは保留中のUI更新をたくさん引き起こします。これは、UIスレッドがキューに入れられた要求にサービスを提供しているため、UIをロックします。

+0

リストをスレッドセーフにする方法についてもっと教えてください。 –

+0

また、テストを実行するたびに約10のプロキシを読み込むので、あまりにも多くはありません。私はparallel.foreachの後に行を追加して、ラベルチェッカーを 'checker is complete!'に変更しました。私がチェックボタンを押すと、すべてのプロセスが完了するまで待つことになるので、それはすべて同じように実行されますが、同じスレッドで同時に実行されるようです同じUIスレッドからのhttpwebrequestはまったく同じ方法でハングアップします。 –

2

好奇心が強い人は、わかりましたが、それが良いプログラミングかどうか、問題を解決する方法がわかりません。

私はそうのような新しいスレッド作成:

Thread t = new Thread(do_checks); 
t.Start(); 

とdo_checks(内部並列もののすべてを片付けを)。

いいと思われます。

1

これは私があなたのコードベースで起こっているかもしれないと思います。

通常のシナリオ:ボタンをクリックします。 Parallel.Foreachループを使用しないでください。Dispatcherクラスを使用し、コードをバックグラウンドで別のスレッドで実行するようにプッシュします。バックグラウンドスレッドの処理が完了すると、UIを更新するためのメインUIスレッドが呼び出されます。このシナリオでは、バックグラウンド・スレッド(Dispatcherを介して呼び出されます)は、メインUIスレッドについて認識しており、コールバックが必要です。単に、メインのUIスレッドが独自のアイデンティティを持っていると言います。

Parallel.Foreachループの使用:Paralle.Foreachループを呼び出すと、フレームワークはスレッドプールスレッドを使用します。 ThreadPoolスレッドはランダムに選択され、実行中のコードは選択されたスレッドのアイデンティティについて決して仮定しないでください。元のコードでは、Parallel.Foreachループを介して呼び出されるディスパッチャスレッドは、それが関連付けられているスレッドを把握することができません。明示的なスレッドを使用すると、明示的なスレッドは実行中のコードに依存する独自のIDを持つため、正常に動作します。

Dispatcherクラスを使用してバックグラウンド・スレッドでコードをプッシュし、そこでロジック全体を実行して全体の実行をスピードアップしたい場合は、理想的にはUIの応答性を維持することが重要です。

6

Parallelステートメントを使用しているときにUIスレッドに書き込めないという問題を回避する良い方法は、Task Factoryと代理人を使用することです。次のコードを参照してください。ディレクトリ、および各ファイルはUIスレッドが合図と更新され、処理された後、並列foreachループでそれらをプロセスへ:

var files = GetFiles(directoryToScan); 

tokenSource = new CancellationTokenSource(); 
CancellationToken ct = tokenSource.Token; 

Task task = Task.Factory.StartNew(delegate 
{ 
    // Were we already canceled? 
    ct.ThrowIfCancellationRequested(); 

    Parallel.ForEach(files, currentFile => 
    { 
     // Poll on this property if you have to do 
     // other cleanup before throwing. 
     if (ct.IsCancellationRequested) 
     { 
      // Clean up here, then... 
      ct.ThrowIfCancellationRequested(); 
     } 

     ProcessFile(directoryToScan, currentFile, directoryToOutput); 

     // Update calling thread's UI 
     BeginInvoke((Action)(() => 
     { 
      WriteProgress(currentFile); 
     })); 
    }); 
}, tokenSource.Token); // Pass same token to StartNew. 

task.ContinueWith((t) => 
     BeginInvoke((Action)(() => 
     { 
      SignalCompletion(sw); 
     })) 
); 

そして、実際のUIの変更を行う方法:

void WriteProgress(string fileName) 
{ 
    progressBar.Visible = true; 
    lblResizeProgressAmount.Visible = true; 
    lblResizeProgress.Visible = true; 

    progressBar.Value += 1; 
    Interlocked.Increment(ref counter); 
    lblResizeProgressAmount.Text = counter.ToString(); 

    ListViewItem lvi = new ListViewItem(fileName); 
    listView1.Items.Add(lvi); 
    listView1.FullRowSelect = true; 
} 

private void SignalCompletion(Stopwatch sw) 
{ 
    sw.Stop(); 

    if (tokenSource.IsCancellationRequested) 
    { 
     InitializeFields(); 
     lblFinished.Visible = true; 
     lblFinished.Text = String.Format("Processing was cancelled after {0}", sw.Elapsed.ToString()); 
    } 
    else 
    { 
     lblFinished.Visible = true; 
     if (counter > 0) 
     { 
      lblFinished.Text = String.Format("Resized {0} images in {1}", counter, sw.Elapsed.ToString()); 
     } 
     else 
     { 
      lblFinished.Text = "Nothing to resize"; 
     } 
    } 
} 

・ホープこれは役に立ちます!あなたはクリックボタンなどのGUIコントロールに並列foreachのを使用したい場合は

+0

UIスレッドに 'BeginInvoke'を使ってパフォーマンスをさらに向上させ、' Invoke'を使っているときに、現在更新中に待つ必要はありません。もちろん、これは 'WriteProgress'の中でロックを必要とするかもしれません... –

+0

UIスレッドは現在ロックされていません、それは完全に応答し続けるので、私はこれがどのように役立つかもしれないのか分かりませんか? しかし、私はその違いを見てみるつもりだ、オブジェクト自体をロックすることは私のシナリオでは問題ではない。 – StevenVL

+0

ロックについての私のコメントを無視しました - 私は 'BeginInvoke'を実行する複数のスレッドについて考えていましたが、それらはすべてUIスレッド上で呼び出されているので、再入可能はありません。私が言っていたことは 'Parallel.ForEach'の各スレッドが' Invoke'が完了するのを待たなければならないということです。 'BeginInvoke'では、UIアップデートがUIスレッドにキューイングされ、非同期で実行されます。 –

0

など その後、

private void start_Click(object sender, EventArgs e) 
     { 
       await Task.Factory.StartNew(() => 
        Parallel.ForEach(YourArrayList, (ArraySingleValue) => 
        { 

       Console.WriteLine("your background process code goes here for:"+ArraySingleValue); 
        }) 
        ); 
    }//func end 

ようTask.Factory.StartNew に並列foreachのを入れて、それは/スタックフリーズ解決したりしますハングの問題

関連する問題