سؤال

I don't have a problem (yet), since my application works, but I want to understand what's going on so I don't get into trouble later. (I'm primarily a database/web application programmer, so threads aren't normally my thing!)

Short version: does the UI need to lock form data when multiple threads are updating it?

I have a simple Winforms application (using DataGridViews) that scans files for certain content and displays the results to the user. I at first did this all in the UI thread but learned that was not a good idea and settled on using ThreadPool.QueueUserWorkItem for [ProcessorCount] collections of items. This works fine, using delegates and BeginInvoke functionality to fire results to the UI as they are found. (Though sometimes there are too many results and the UI still lags, but that's another problem.)

The worker threads are completely isolated, doing their own thing. I understand the concept of multiple threads and needing to be aware of accessing shared data at the same time. What I'm not entirely clear on, however, is what happens when the various threads call the UI thread to update it. Only the UI will be updating controls (and form-level variables) but since the calls come from the other threads, how does this play out? For example, if I'm adding items to a List or incrementing a counter, can a call from one worker thread interrupt another? Each BeginInvoke is it's own call, but modifying the data seems like it could still be a problem.

None of the examples of BeginInvoke I've found mention any need for locking in the UI. These two topics are related but still don't give the exact answer I'm looking for:

Using Control.Invoke() in place of lock(Control)

What's the difference between Invoke() and BeginInvoke()

هل كانت مفيدة؟

المحلول

For example, if I'm adding items to a List or incrementing a counter, can a call from one worker thread interrupt another? Each BeginInvoke is it's own call, but modifying the data seems like it could still be a problem.

No, an invoke call cannot be interrupted by another such call. In fact, it cannot be interrupted by any other operation on the UI thread. This is the reason that if you put your UI thread to work the UI itself "hangs" (input and paint messages get queued for processing, but they cannot interrupt the work you are doing).

However, adding items to a List<T> (is that what you mean?) or incrementing a counter are not UI manipulations. Why are you doing them on the UI thread?

It's true that invoking such operations on the UI thread gives you thread-safe locking as a side effect, but it's not really free: it's much more expensive than doing a simple lock on the worker thread.

You should consider switching to this approach, which will also solve your "too many updates = lag" problem:

  • Data variables (lists, counters, whatever) are manipulated directly by worker threads. Use appropriate locking constructs (lock, Interlocked methods, concurrent collections, whatever) to provide thread-safety.
  • Whenever a worker thread modifies a data variable, it also sets a global modified flag to true.
  • Nothing gets marshalled to the UI thread (invoked).
  • Meanwhile, the UI thread sets up a timer to fire every e.g. half second. Whenever the timer fires, the UI thread checks the value of modified and resets it to false (you can do this lock-free with Interlocked.CompareExchange).
  • If the UI thread finds that data has been modified, it locks all data variables and repaints the UI as necessary.

This way you only get at most one (expensive!) UI update on every timer tick, and the UI will not lag no matter how much data is coming in from the worker threads. Plus, you can pick the timer interval to be as short or as long as you like.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top