Reputation: 97
I am trying to implement a Parallel.ForEach loop to replace an old foreach
loop, but I am having trouble updating my UI (i have a counter showing something like 'x/y files processed'). I have made a Parallel Forloop
example to illustrate my problem (the label is not being updated).
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 click event method, and add a Thread.Sleep() it seems to give time for the UI updating thread to do 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 no way of avoiding the sleep, or do I need to have it there? it seems like my ui won't update the label unless I do? which I find strange, since I can move the application window (it doesn't lock up) - so why won't the label update, and how can I alter my code to better support Parallel.For(Each) and UI updates?
I've been searching for a solution, but I can't seem to find anything (or I maybe searching for the wrong thing?).
Regards Simon
Upvotes: 4
Views: 10346
Reputation: 6452
I have a similar requirement to update my GUI as the results come in on a Parallel.ForEach(). I went a very different way than you did, though.
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 would then have a 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 point I'm trying to make here is you can determine when it makes sense to determine your progress through you loop. And, as these loop iterations are on background threads you will need to marshall the GUI control update logic back onto your main (dispatching) thread. This is just a simple example - just make sure you follow the concept and you'll be fine.
Upvotes: 4
Reputation: 28050
My guess is that counting to 25 million (in parallel!) takes less than a second... therefore your timer will not be fired before the counting is complete. If you add the Thread.Sleep
, the whole thing will run much slower so that you can see the updates.
On the other hand, your timer event handler looks messy. You spawn a thread to post a message to your UI, and when you are finally on your UI thread, you call Application.DoEvents... why? You should be able to remove both the task creation and DoEvents call.
Edit: I tested the program you posted, and saw the label update twice. My computer takes more than one second to count to 25m. I increased the number to 1 billion, and the lable updates multiple 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 number displayed is not the number of items currently processed, but the index of the item that happened to be processed in the moment the timer event is raised. You will have to implement the counting yourself. This can be done by using
Interlocked.Add(ref m_count, 1);
Upvotes: 2
Reputation: 4032
for parallel try
//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 it might not work. u might got thread exception becus winform control won't allow another thread that is not creater thread update it. if that is the case, try create another method and use control itself invoke as delegate.
eg. label1.Invoke(updateUIHandler);
Upvotes: 1