Originally posted on: http://bobgoedkoop.nl/akraus1/archive/2014/10/12/159645.aspx
Although there was no part one my other post "Can You Execute Code While Waiting For A Lock?" can be considered as part one. I hope that this will not become a larger series where code is executed in the wrong order. Suppose you want to execute the commands
- Init
- Action
- OtherAction
- Dispose
- Shutdown
in this order on the UI thread of an application from another thread. Then you would do something like this:
using System;using System.Diagnostics;using System.Threading;using System.Windows.Forms;namespace MessagePumping {publicpartialclass Form1 : Form {public SynchronizationContext WindowsFormsSynchronizationContext;public Form1() { InitializeComponent(); var tmp = this.Handle; WindowsFormsSynchronizationContext = System.Windows.Forms.WindowsFormsSynchronizationContext.Current; BlockFor(); } void BlockFor() { Barrier b = new Barrier(2); Thread t = new Thread(() => {lock (this) { b.SignalAndWait(); // let main UI thread run into lock Thread.Sleep(300); // enter the lock on the UI thread Action otherAction = () => Debug.Print("OtherAction"); WindowsFormsSynchronizationContext.Post((o) => Debug.Print("Init"), null); WindowsFormsSynchronizationContext.Post((o) => Debug.Print("Action"), null); WindowsFormsSynchronizationContext.Post((o) => otherAction(), null); WindowsFormsSynchronizationContext.Post((o) => Debug.Print("Dispose"), null); WindowsFormsSynchronizationContext.Post((o) => Debug.Print("Shutdown"), null); } }); t.Start(); b.SignalAndWait(); lock (this) // deadlock which never releases lock { } } }This will print the messages in order just as you would expect it:
Init
Action
OtherAction
Dispose
Shutdown
Now lets slightly change the code of BlockFor
void BlockFor() { Barrier b = new Barrier(2); Thread t = new Thread(() => {lock (this) { b.SignalAndWait(); // let main UI thread run into lock Thread.Sleep(300); // enter the lock on the UI thread
Action otherAction = () => Debug.Print("OtherAction"); WindowsFormsSynchronizationContext.Post((o) => Debug.Print("Init"), null); WindowsFormsSynchronizationContext.Post((o) => Debug.Print("Action"), null); WindowsFormsSynchronizationContext.Post((o) => { WindowsFormsSynchronizationContext.Send((o2) => otherAction(), null); },null); WindowsFormsSynchronizationContext.Post((o) => Debug.Print("Dispose"), null); WindowsFormsSynchronizationContext.Post((o) => Debug.Print("Shutdown"), null); } }); t.Start(); b.SignalAndWait(); lock (this) // deadlock which never releases lock { } }
and check if during a post a send operation is working as expected:
InitAction
OtherAction
Dispose
Shutdown
The output is till the same. So far so good. Now keep the code but change something else and now we get:
Init
Action
Dispose
Shutdown
OtherAction
What has changed?
…
…
Still not it.
The SynchronizationContext! You were thinking you were using a WindowsFormsSynchronizationContext? No you weren´t. The WindowsFormsSynchronizationContext.Current is returning not a WindowsFormsSynchronizationContext but a SynchronizationContext instance. The reason is that the static Current property comes from SynchronizationContext. You were using the current SynchronizationContext whatever it was. In the first run it was actually a WPF DispatcherSynchronizationContext instance which did work just as expected. But if we actually force the usage of an WindowsFormsSynchronizationContext by setting it explictely
public Form1() { InitializeComponent(); var tmp = this.Handle; SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext()); WindowsFormsSynchronizationContext = System.Windows.Forms.WindowsFormsSynchronizationContext.Current; BlockForThreeSeconds2(); }Then we get the faulty behavior that shutdown is executed before our OtherAction which was not our intention. The first question is why are we getting this? When using window messages you are adding messages to a queue. When you post a message you add it at the end of the queue. The same happens if you Send a messages with the only exception that the call blocks until all pending messages and your message have been processed. Our OhterAction message is special since it calls Send to the message pump while it is being processed. The logical picture you have in mind is
but what actually happens is that while your wrapper call is executed it enqueues OtherAction at the end of the message queue and waits until OtherAction has been called. But since the Dispose and Shutdown messages come first our OtherAction message will be called far too late.
Ok that explains why WinForms suck. But why did it work with the WPF Dispatcher? The WPF dispatcher uses a little optimization: If you try to enqueue a message on the thread that runs the message pump then it will NOT be enqueued but directly executed. The WPF Dispatcher model is therefore much saner thane Winforms and prevents you from shooting yourself in the foot. If you do not use the WinForms synchronization context but you call directly on the Control.Begin/Invoke you can fix this faulty model by calling InvokeRequired
Action otherAction = () => Debug.Print("OtherAction");this.BeginInvoke((Action) (() => Debug.Print("Init")));this.BeginInvoke((Action) (() => Debug.Print("Action")));this.BeginInvoke((Action) (() => {if (this.InvokeRequired) // only enqueue to message queue if we are beeing called from a different thread {this.Invoke((Action)(() => otherAction())); }else { otherAction(); // otherwise execute the callback directly } }));this.BeginInvoke((Action) (() => Debug.Print("Dispose")));this.BeginInvoke((Action) (() => Debug.Print("Shutdown")));
The rule to check with InvokeRequiredworks before actually enqueuing messages works well and helps to alleviate the problem of unintended message reordering. Unfortunately this does not work with WindowsFormsSynchronizationContexts since it has no InvokeRequired method to do the check by yourself. Until then your best bet would be to abandon the usage of WindowsFormsSynchronizationContext because of this or you simply use the WPF DispatcherSynchronizationContext which works well. But you should be aware that you then have up to 3 message queues in parallel. First it is the window message queue of your window. Then comes the Winforms message queue which uses its own window message to start working on queued messages. And as last queue you have the WPF message queue which also has its own private message queue and window message.
If you interleave WPF and WinForms messages in some calling code like this
public WindowsFormsSynchronizationContext WinFormsContext;public DispatcherSynchronizationContext WPFContext;public Form1() { InitializeComponent(); var tmp = this.Handle; WinFormsContext = new System.Windows.Forms.WindowsFormsSynchronizationContext(); WPFContext = new DispatcherSynchronizationContext(); BlockFor(); }void BlockFor() { Barrier b = new Barrier(2); Thread t = new Thread(() => {lock (this) { b.SignalAndWait(); // let main UI thread run into lock Thread.Sleep(300); // enter the lock on the UI thread Action otherAction = () => Debug.Print("OtherAction"); WPFContext.Post((o) => Debug.Print("Init"), null); WinFormsContext.Post((o) => Debug.Print("WinForms Init"),null); WPFContext.Post((o) => Debug.Print("Action"), null); WinFormsContext.Post((o) => Debug.Print("WinForms Action"), null); WPFContext.Post((o) => WPFContext.Send((o2) => otherAction(), null), null); WPFContext.Post((o) => Debug.Print("Dispose"), null); WinFormsContext.Post((o) => Debug.Print("WinForms Dispose"), null); WPFContext.Post((o) => Debug.Print("Shutdown"), null); WinFormsContext.Post((o) => Debug.Print("WinForms Shutdown"), null); } }); t.Start();
then you do not get interleaved messages as the calling code suggests but this:
Init
WinForms Init
WinForms Action
WinForms Dispose
WinForms Shutdown
Action
OtherAction
Dispose
Shutdown
This is another subtle difference between WPF and Windows Forms message pumping. WPF will execute only one posted message whereas Winforms will execute all pending posted messages at once until there are no more left to execute. If you have a mixed WPF/Winforms application and you want to get rid of Invokes in favor of BeginInvokes because message pumping is evil you just are about to find the next trap door late during your system integration test. If you can you should stick to only one message queue and prefer Invoke/Send where you can. If you are using Winforms BeginInvoke you not only do not know when it will be run (perhaps too late) but if you need coordination with WPF window messages you are out of luck in this scenario. I am sure you will not design a system this way but at some day someone will add a hidden dependency between both message pumps leaving you little chances to design you out of this mess.
I hope that MS will address these (obvious?) deficiencies in Winforms message pumping so we do not have to worry about stuff we never wanted to know in such great detail in the first place.
Update 1
No I will not make a different post out of it. The last example has an interesting twist if you are using the WPF Dispatcher. It has changed between .NET Framework 4.0 and 4.5. Depending on the target platform you are compiling for (.NET 4.0 or 4.5+) you will get different behavior. The .NET 4.0 Dispatcher WILL pump messages just like WinForms. This means the code above for the WPF context post messages only returns for .NET 4.5:
Init
Action
OtherAction
Dispose
Shutdown
But if you compile for .NET 4.0
you get
Init
Action
Dispose
Shutdown
OtherAction
just like in the bad case of Windows Forms. The magic happens in the DispatcherSynchronizationContext class at:
publicoverridevoid Send(SendOrPostCallback d, object state) {if (BaseCompatibilityPreferences.GetInlineDispatcherSynchronizationContextSend() && this._dispatcher.CheckAccess()) {this._dispatcher.Invoke(DispatcherPriority.Send, d, state);return; }this._dispatcher.Invoke(this._priority, d, state); }
This returns true or false depending on what is set in the TargetFramwork attribute of your currently executing assembly.
[assembly: TargetFramework(".NETFramework,Version=v4.5.2", FrameworkDisplayName = ".NET Framework 4.5.2")]
Interesting. You can find the future .NET Framework version numbers (but not their release dates) in the BinaryCompatiblityMap which contains the promising name AddQuirksForFramework.
if (buildAgainstVersion >= 50000) {this.TargetsAtLeast_Desktop_V5_0 = true; }if (buildAgainstVersion >= 40504) {this.TargetsAtLeast_Desktop_V4_5_4 = true; }if (buildAgainstVersion >= 40503) {this.TargetsAtLeast_Desktop_V4_5_3 = true; }if (buildAgainstVersion >= 40502) {this.TargetsAtLeast_Desktop_V4_5_2 = true; }if (buildAgainstVersion >= 40501) {this.TargetsAtLeast_Desktop_V4_5_1 = true; }if (buildAgainstVersion >= 40500) {this.TargetsAtLeast_Desktop_V4_5 = true;this.AddQuirksForFramework(TargetFrameworkId.Phone, 70100);this.AddQuirksForFramework(TargetFrameworkId.Silverlight, 50000);return; }…
So we can expect a .NET 4.5.3, 4.5.4 and .NET 5.0 in the future with some confidence now.