3

Let's assume that the code running in the thread that instantiated the form/control/element (usually the main thread) does not modify/access that element at the same time, is it possible to:

  1. get the Text property of a TextBox.

  2. enumerate a ListView.

  3. subscribe to a Form's Closing event. (Knowing that the hook will be called from the thread that instantiated that form)

I have tried all 3 and the program doesn't seem to complain about it. I had always thought that you had to invoke any call that wants to even remotely touch anything UI related (read or write).

I understand very clearly why I need to use the IsInvokeRequired/Invoke pattern when modifying an element, but I cannot see why accessing properties/events would cause any problems.

3 Answers 3

5

It's definitely possible, however, it could lead to unexpected behaviour. Also, other thread-related bugs also need to be taken into consideration e.g. race conditions/deadlocks see Managed Threading Best Practises.

I would always stick to accessing the UI on the UI thread to be on the safe side.

Sign up to request clarification or add additional context in comments.

3 Comments

So, basically, even if I follow every threading best practice, there could still be some undocumented weirdness happening?
Well by not accessing a UI component on the UI thread your not following best practices. If you start messing about with cross-thread access it's bound to blow up in your face at some point, but not necessarily right away.
@Deli At the end of the day all that matters is that only one thread ever accesses a Control at one time. By far the easiest way to enforce this is to only allow one thread to access any control, ever, because then you can guarantee that there will never be another thread modifying the control at the same time. If you can ensure no other thread is ever modifying the control you could access it from a non-UI thread, but due to the complexities involved in GUI applications, that's not recommended.
3

And what are you doing to ensure that the UI thread isn't modifying the control while you're reading from it? The whole reason for marshaling to the UI thread is so that you don't need to worry about that case. Enumerating the listbox in particular is going to be the easiest to break as it will take the longest time (this creating the largest window for a race condition). You should be marshaling to the UI thread for all 3 of those things.

Comments

2

This is not safe. The UI thread could (and probably will) change the state of the control while you are reading it from another thread. Your reads might catch the control in a half-baked state. It may appear to be working now, but sooner or later it will fail...probably unpredictably and spectacularly.

No, you probably do not need to use the Invoke pattern. To be quite frank...that pattern is usually the worst option. It is usually better to have your worker thread do the heavy lifting and then send new data or progress information to the UI thread via a queue and let the UI thread pick this up via a timer. This has several advantages.

  • Eliminates the expensive Invoke and BeginInvoke operations.
  • The UI thread gets to decide when and how often it should update itself with new data instead of having the worker thread dictate this.
  • You get more throughput on the worker thread since it doesn't have to wait for Invoke to return.
  • There is no risk of overrunning the UI message queue like there would be with BeginInvoke.
  • It decouples the UI and worker thread interactions.

You will need to enumerate the ListView and build up a separate data structure that can then be accessed by the worker thread safely. If the ListView contains a lot of items consider maintaining a separate collection in tandem with the control. That way you spread out the penalty of coping the data over a bigger period of time in which it probably will not be noticed as much. Afterall, we do not want the copy operation to freeze the UI thread otherwise a user will notice. A ConcurrentBag or the like might be a good choice since it can be safely modified by the UI thread while the worker thread is reading it.

2 Comments

Invoke and BeginInvoke aren't really all that expensive... in fact having a seperate dedicated timer would probably be more resource intensive. For your second point, you can buffer the updates if there are going to be a lot of them. For 3, if it's an issue in that context you can use a non-blocking version, for 4, again, it can be solved by buffering the output, and 5, is a particularly good point. I prefer different means of solving that problem than using a timer though. For later versions you can use the IProgress interface.
@Servy: Your points are all good and well taken. There's are sorts of things you can do to mitigate my points. In the end...when I go the ISynchronizeInvoke route the code always ends up smelling.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.