Reputation: 1731
Let's say I have a method that I run in a separate thread via Task.Factory.StartNew()
.
This method reports so many progress (IProgress
) that it freezes my GUI.
I know that simply reducing the number of reports would be a solution, like reporting only 1 out of 10 but in my case, I really want to get all reports and display them in my GUI.
My first idea was to queue all reports and treat them one by one, pausing a little bit between each of them.
Firstly: Is it a good option?
Secondly: How to implement that? Using a timer or using some kind of Task.Delay()
?
UPDATE:
I'll try to explain better. The progress sent to the GUI consists of geocoordinates that I display on a map. Displaying each progress one after another provide some kind of animation on the map. That's why I don't want to skip any of them.
In fact, I don't mind if the method that I execute in another thread finishes way before the animation. All I want, is to be sure that all points have been displayed for at least a certain amount of time (let's say 200 ms).
Upvotes: 1
Views: 163
Reputation: 28355
I have many concerns regarding your solution, but I can't say for sure which one can be a problem without code samples.
First of all, Stephen Cleary in his StartNew
is Dangerous article points out the real problem with this method with using it with default parameters:
Easy enough for the simple case, but let’s consider a more realistic example:
private void Form1_Load(object sender, EventArgs e) { Compute(3); } private void Compute(int counter) { // If we're done computing, just return. if (counter == 0) return; var ui = TaskScheduler.FromCurrentSynchronizationContext(); Task.Factory.StartNew(() => A(counter)) .ContinueWith(t => { Text = t.Result.ToString(); // Update UI with results. // Continue working. Compute(counter - 1); }, ui); } private int A(int value) { return value; // CPU-intensive work. }
... Now, the question returns: what thread does
A
run on? Go ahead and walk through it; you should have enough knowledge at this point to figure out the answer.
Ready? The methodA
runs on a thread pool thread the first time, and then it runs on theUI
thread the last two times.
I strongly recommend you to read whole article for better understanding the StartNew
method usage, but want to point out the last advice from there:
Unfortunately, the only overloads for
StartNew
that take aTaskScheduler
also require you to specify theCancellationToken
andTaskCreationOptions
. This means that in order to useTask.Factory.StartNew
to reliably, predictably queue work to the thread pool, you have to use an overload like this:Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
And really, that’s kind of ridiculous. Just use
Task.Run(() => A());
.
So, may be your code can be easily improved simply by switching the method you are creating news tasks. But there is some other suggestions regarding your question:
BlockingCollection
for the storing the reports, and write a simple consumer from this queue to UI, so you'll always have a limited number of reports to represent, but at the end all of them will be handled. ConcurrentExclusiveSchedulerPair
class for your logic: for generating the reports use the ConcurrentScheduler
Property and for displaying them use ExclusiveScheduler
Property.Upvotes: 1
Reputation: 19081
Sounds like the whole point of having the process run in a separate thread is wasted if this is the result. As such, my first recommendation would be to reduce the number of updates if possible.
If that is out of the question, perhaps you could revise the data you are sending as part of each update. How large, and how complex is the object or data-structure used for reporting? Can performance be improved by reducing it's complexity?
Finally, you might try another approach: What if you create a third thread that just handles the reporting, and delivers it to your GUI in larger chunks? If you let your worker-thread report it's status to this reporter-thread, then let the reporter thread report back to your main GUI-thread only occasionally (e.g. every 1 in 10, as you suggest yourself above, bur then reporting 10 chunks of data at once), then you won't call on your GUI that often, yet you'll still be able to keep all the status data from the processing, and make it available in the GUI.
I don't know how viable this will be for your particular situation, but it might be worth an experiment or two?
Upvotes: 2