How to solve threading sync issues with unknown number of events in .NET?
We're using WebClient DownloadDataCompleted
& DownloadProgressChanged
events. We noticed that the progress event can fire an indeterminate number of times and not return from each callback, and still the DataCompleted
event will fire. The reason DownloadProgressEvent
doesn't return is because it's updating the ProgressBar
on the form, which is going through a Control.Invoke
cycle. We don't use BeginInvoke
for other reasons (Progres开发者_JAVA百科sBar
max and min change constantly and this cause assertions, since we can't sync up progress updates with progress bar max/min settings).
The question is: Whats the best approach for this?
Simply put, we don't want to acknowledge a completed download until the ProgressBar
finishes updating. This implies something of a reverse semaphore that counts up, and is set when it goes back down to zero. We could just use a counter to increment/decrement when entering/leaving DownloadProgress
callback, but I would have thought there's something more OS specific.
I would go for the following:
DownloadDataCompleted += delegate {
progressBar.Invoke(() => {
progressBar.Value = progressBar.Maximum;
progressFinished = true;
}
};
DownloadProgressChanged += delegate {
progressBar.Invoke(() => {
if (!progressFinished)
progressBar.Value =
progressBar.Minimum +
(progressBar.Maximum - progressBar.Minimum) * progressRatio;
}
};
There should be no problems with changing the Maximum and Minimum, since all the changes occur anyway in the UI thread.
(The code which changes Maximum
or Minimum
must take care about recalculating the Value
, of course.)
P.S.: Edited the post, taking @Ben Voigt's suggestion into account.
I would be inclined to use BeginInvoke to handle the control update, but arrange for the an atomic update of fields holding the requested "current" and "maximum" values, along with a flag that indicates an update has been requested. The UI's update routine should not hold any locks while performing the update. If locks are used, the UI should grab the lock, make a local copy of the values (including the flag), clear the flag, release the lock, and redraw if necessary. The routine (on the other thread) which wants to request the update should grab the lock, update the values, and--if the flag isn't already set--set the flag and perform a BeginInvoke.
That will ensure that no matter how often the non-UI thread requests an update, at most one BeginInvoke will be outstanding. Further, every update will have usable values for "current" and "maximum".
精彩评论