Windows forms work on top of the Windows messaging infrastructure. That means that a lot of the operations you perform on the controls in question are actually delegated to Windows to support proper native behaviour of all the controls.
Label
doesn't change the default implementation, and it doesn't cache the text in managed code by default. This means that it uses the SetWindowText
native method to set the current label text (and correspondingly, GetWindowText
to read it), which posts a WM_SETTEXT
to the message loop. The real update happens on the thread that handles the message loop, also known as the UI thread. Unless you go out of your way to prohibit this kind of call (Control.checkForIllegalCrossThreadCall
in current reference source), it will work. By default, this is set depending on whether a debugger is attached - so your code may crash while debugging, but will work outside of a debugger, since SetWindowText
happens to be thread-safe. There's other parts of the Text
property that may or may not be thread-safe, but if you're lucky, everything works just fine.
You can set Control.CheckForIllegalCrossThreadCall
to true
explicitly, and I'd recommend you to do so. Accessing any resource from multiple threads is prone to hard to debug issues, and marshalling whatever work needs to be done on the UI to the UI thread is... kind of the job of the UI thread anyway.
Manipulating the UI exclusively from the UI thread gives you quite important benefits:
- Predictability and realiability - things will tend to happen in certain reliable orders. If I have a hundred threads setting the
Text
of two different controls, delegating the UI update to the UI thread will ensure that the two controls always have consistent values, while updating directly from the background threads will tend to interleave the updates "randomly". In a real application, this can cause confusion as well as hard to find bugs. Note that this isn't absolute - any await
/Application.DoEvents
may or may not disrupt this. But even in that case, you have well defined synchronization points, rather than pre?mptive multi-tasking.
- Multi-threading is hard. Most things you work with aren't thread-safe, and even allegedly thread-safe operations may have multi-threading bugs or simply complicated behaviour when running in a MT scenario. Even a simple thing like updating two booleans in a sequence becomes dangerous, and may introduce hard to debug bugs. Your best bet is to keep as many things thread-affine as you can. You'll usually find you can confine all interfaces between the threads to a tiny portion of your code, which makes them much easier to test and debug.
- It's really cheap when you've already separated "work" from "UI" anyway. And that's a pretty handy design practice on its own.
As a side-note, you usually want to await
tasks that you spin off, or handle exceptions explicitly. If you enable Control.CheckForIllegalCrossThreadCall
, the label will no longer update, but it will not show an exception either - by default, threadpool threads ignore unhandled exceptions nowadays (since .NET 4.5.2 IIRC). The await
will marshall any exceptions (and return values) back to the UI thread.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…