C # Parallel.For and updating the user interface?

I am trying to implement a Parallel.ForEach loop to replace the old foreach loop, but I am having problems updating my user interface (I have a counter showing something like "processed x / y files"). I made an example of a Parallel.For loop to illustrate my problem (label not updating).

using System; using System.Windows.Forms; using System.Threading.Tasks; using System.Threading; namespace FormThreadTest { public partial class Form1 : Form { private SynchronizationContext m_sync; private System.Timers.Timer m_timer; private int m_count; public Form1() { InitializeComponent(); m_sync = SynchronizationContext.Current; m_count = 0; m_timer = new System.Timers.Timer(); m_timer.Interval = 1000; m_timer.AutoReset = true; m_timer.Elapsed += new System.Timers.ElapsedEventHandler(m_timer_Elapsed); m_timer.Start(); } private void m_timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { Task.Factory.StartNew(() => { m_sync.Post((o) => { label1.Text = m_count.ToString(); Application.DoEvents(); }, null); }); } private void button1_Click(object sender, EventArgs e) { Task.Factory.StartNew(() => { Parallel.For(0, 25000000, delegate(int i) { m_count = i; }); }); } } } 

If I change the button event event method and add Thread.Sleep (), it seems that the time for updating the UI thread does its job:

 private void button1_Click(object sender, EventArgs e) { Task.Factory.StartNew(() => { Parallel.For(0, 25000000, delegate(int i) { m_count = i; Thread.Sleep(10); }); }); } 

Is there a way to avoid sleep, or do I need to eat it there? It seems that my ui will not update the shortcut if I do not? which I find strange, since I can move the application window (it doesnโ€™t block) - so why not replace the shortcut and how can I change my code to better support Parallel.For updates (each) and UI?

I was looking for a solution, but I can not find anything (or maybe I was looking for the wrong thing?).

Relationship Simon

+4
source share
3 answers

I assume that counting up to 25 million (in parallel!) Takes less than a second ... so your timer will not start until the count is complete. If you add Thread.Sleep , it will all work a lot slower so you can see the updates.

On the other hand, your timer event handler looks dirty. You create a thread to post a message in your user interface, and when you finally in the user interface thread, you call Application.DoEvents ... why? You must be able to remove both task creation and DoEvents.

Edit: I tested the program you published and saw the tag update twice. My computer takes more than one second to count to 25 m. I have increased the number to 1 billion, and lable is updated several times.

Edit2: you can reduce the timer handler to

  private void m_timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { m_sync.Post((o) => { label1.Text = m_count.ToString(); }, null); } 

Unfortunately, the displayed number is not the number of elements being processed, but the index of the element that was processed at the time the timer event occurred. You will have to do the count yourself. This can be done using

 Interlocked.Add(ref m_count, 1); 
+2
source

I have a similar requirement to update my GUI as the results come in Parallel.ForEach (). I went completely different than you.

 public partial class ClassThatUsesParallelProcessing { public event ProcessingStatusEvent StatusEvent; public ClassThatUsesParallelProcessing() { } private void doSomethingInParallel() { try { int counter = 0; int total = listOfItems.Count; Parallel.ForEach(listOfItems, (instanceFromList, state) => { // do your work here ... // determine your progress and fire event back to anyone who cares ... int count = Interlocked.Increment( ref counter ); int percentageComplete = (int)((float)count / (float)total * 100); OnStatusEvent(new StatusEventArgs(State.UPDATE_PROGRESS, percentageComplete)); } } catch (Exception ex) { } } } 

Your GUI will have something similar to the following:

 private void ProcessingStatusEventHandler(object sender, StatusEventArgs e) { try { if (e.State.Value == State.UPDATE_PROGRESS) { this.BeginInvoke((ProcessHelperDelegate)delegate { this.progressBar.Value = e.PercentageComplete; } } } catch { } } 

The only thing I'm trying to do here is to determine when it makes sense to determine your progress through the cycle. And since these loop iterations are in the background thread, you will need to bring the GUI control update logic back to your main (dispatch) thread. This is just a simple example - just make sure you follow the concept and everything will be fine.

+4
source

for parallel attempt

 //private void m_timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) System.Threading.Tasks.Task.Factory.StartNew(() => { m_sync.Post((o) => { label1.Text = m_count.ToString(); Application.DoEvents(); }, null); System.Threading.Thread.Sleep(1000;) }, System.Threading.Tasks.TaskCreationOptions.LongRunning); 

but that may not work. u may get a thread exception because winform control will not allow another thread that does not create an update thread to update it. if so, try to create another method and use the call itself as a delegate.

 eg. label1.Invoke(updateUIHandler); 
+1
source

Source: https://habr.com/ru/post/1336796/


All Articles