web-dev-qa-db-ja.com

このParallel.ForEachコードがプログラムをフリーズさせるのはなぜですか?

その他の初心者の質問:

このコードは、メインウィンドウのリストからいくつかのプロキシを取得し(異なる関数間で変数を使用できるようにする方法がわかりませんでした)、それぞれをチェックして(単純なhttpwebrequest)、次のリストに追加します。 finishProxies。

なんらかの理由でスタートボタンを押すと、プログラム全体がハングアップします。 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を変更しようとしましたが、スタートボタンが押された後にプログラムがフリーズします。以前はうまくいきましたが、Threadクラスを使用しました。

Parallel.Foreach内からUIを更新するにはどうすればよいですか?また、Parallel.Foreachを機能させて、動作中にUIがフリーズしないようにするにはどうすればよいですか?

これがコード全体です。

18
dsp_099

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

更新:または、新しいスレッドを手動で作成し、その中で処理を開始することもできます。それも悪いことではありません。

また、Jim Mischelが指摘しているように、複数のスレッドから同時にリストにアクセスしているため、競合状態が発生します。 ConcurrentBagの代わりに List を使用するか、リストにアクセスするたびにリストをlockステートメントで囲みます。

18
Jon

Parallelステートメントを使用しているときにUIスレッドに書き込めないという問題を回避する良い方法は、タスクファクトリとデリゲートを使用することです。次のコードを参照してください。これを使用して、ディレクトリ内の一連のファイルを反復処理します。各ファイルが処理された後、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";
        }
    }
}

お役に立てれば!

9
StevenVL

誰かが興味を持っているなら、私はそれをちょっと理解しましたが、それが良いプログラミングなのか、それとも問題に対処する方法なのかはわかりません。

私は次のような新しいスレッドを作成しました:

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

そして、do_checks()内のすべての並列処理を片付けます。

大丈夫そうです。

3
dsp_099

ボタンクリックなどのGUIコントロールでparallel foreachを使用する場合は、Task.Factory.StartNewにparallel foreachを配置 like

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

フリーズ/スタックまたはハングの問題を解決します

2
Hassan Saeed

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

それがUIのロックアップを引き起こすかどうかはわかりません。より多くの情報がなければ、言うのは難しいです。 proxiesリストが非常に長く、checkProxyの実行に時間がかからない場合、タスクはすべてそのInvoke呼び出しの後ろにキューに入れられます。これにより、保留中のUI更新が大量に発生します。 UIスレッドはキューに入れられたリクエストの処理でビジー状態であるため、UIがロックされます。

1
Jim Mischel

これは、コードベースで発生している可能性があると私が思うことです。

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

Parallel.Foreachループの使用:Paralle.Foreachループを呼び出すと、フレームワークはスレッドプールスレッドを使用します。 ThreadPoolスレッドはランダムに選択され、実行中のコードは選択されたスレッドのIDを想定してはなりません。元のコードでは、Parallel.Foreachループを介して呼び出されたディスパッチャースレッドが、関連付けられているスレッドを認識できない可能性が非常に高くなっています。明示的なスレッドを使用する場合、明示的なスレッドには実行中のコードが信頼できる独自のIDがあるため、正常に機能します。

理想的には、UIの応答性を維持することが主な関心事である場合は、最初にDispatcherクラスを使用してバックグラウンドスレッドでコードをプッシュしてから、実行全体を高速化するロジックを使用する必要があります。

1
Pawan Mishra