Why does this Parallel.ForEach code freeze the program up?

后端 未结 6 1633
南笙
南笙 2020-11-30 13:37

More newbie questions:

This code grabs a number of proxies from the list in the main window (I couldn\'t figure out how to make variables be available between diffe

相关标签:
6条回答
  • 2020-11-30 14:05

    You must not start the parallel processing in your UI thread. See the example under the "Avoid Executing Parallel Loops on the UI Thread" header in this page.

    Update: Or, you can simply create a new thread manuall and start the processing inside that as I see you have done. There's nothing wrong with that too.

    Also, as Jim Mischel points out, you are accessing the lists from multiple threads at the same time, so there are race conditions there. Either substitute ConcurrentBag for List, or wrap the lists inside a lock statement each time you access them.

    0 讨论(0)
  • 2020-11-30 14:09

    If anyone's curious, I kinda figured it out but I'm not sure if that's good programming or any way to deal with the issue.

    I created a new thread like so:

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

    and put away all of the parallel stuff inside of do_checks().

    Seems to be doing okay.

    0 讨论(0)
  • 2020-11-30 14:17

    This is what I think might be happening in your code-base.

    Normal Scenario : You click on button. Do not use Parallel.Foreach loop. Use Dispatcher class and push the code to run on separate thread in background. Once the background thread is done processing, it will invoke the main UI thread for updating the UI. In this scenario, the background thread(invoked via Dispatcher) knows about the main UI thread, which it needs to callback. Or simply said the main UI thread has its own identity.

    Using Parallel.Foreach loop : Once you invoke Paralle.Foreach loop, the framework uses the threadpool thread. ThreadPool threads are chosen randomly and the executing code should never make any assumption on the identity of the chosen thread. In the original code its very much possible that dispatcher thread invoked via Parallel.Foreach loop is not able to figure out the thread which it is associated with. When you use explicit thread, then it works fine because the explicit thread has its own identity which can be relied upon by the executing code.

    Ideally if your main concern is all about keeping UI responsive, then you should first use the Dispatcher class to push the code in background thread and then in there use what ever logic you want to speedup the overall execution.

    0 讨论(0)
  • 2020-11-30 14:18

    One problem with your code is that you're calling FinishedProxies.Add from multiple threads concurrently. That's going to cause a problem because List<T> isn't thread-safe. You'll need to protect it with a lock or some other synchronization primitive, or use a concurrent collection.

    Whether that causes the UI lockup, I don't know. Without more information, it's hard to say. If the proxies list is very long and checkProxy doesn't take long to execute, then your tasks will all queue up behind that Invoke call. That's going to cause a whole bunch of pending UI updates. That will lock up the UI because the UI thread is busy servicing those queued requests.

    0 讨论(0)
  • 2020-11-30 14:22

    if you want to use parallel foreach in GUI control like button click etc then put parallel foreach in Task.Factory.StartNew 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
    

    it will resolve freeze/stuck or hang issue

    0 讨论(0)
  • 2020-11-30 14:25

    A good way to circumvent the problems of not being able to write to the UI thread when using Parallel statements is to use the Task Factory and delegates, see the following code, I use this to iterate over a series of files in a directory, and proces them in a parallel foreach loop, after each file is processed the UI thread is signaled and updated:

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

    And the methods that do the actual UI changes:

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

    Hope this helps!

    0 讨论(0)
提交回复
热议问题