04-04-2012 03:55 PM
I have a component that processes work in a background thread. It posts progress callbacks to the UI thread, in which I plot data in NI WinForms graphs, update axis ranges, and update various other controls. At the end a completion event is posted to the UI thread to do final UI updates.
I noticed some code in my progress event handler was executing after the completion event handler. This was strange because there is just the one background thread processing all the work in order, posting progress events to the UI in order, and posting the completion event to the UI at the end. All the event handlers should execute in order on the UI thread. What I see instead is half of the last progress event handler executes after the completion event handler executes. Parts of earlier progress event handlers sometimes execute out of order, too.
Examining the call stack of the last progress event handler revealed the NI axis range setter ends up calling SynchronizationContext.Send to invoke the RangeChanged event. This causes the an additional WPF dispatcher frame to be pushed and to process any UI events in the queue and finally the RangeChanged event. Then the dispatcher frame is popped and the rest of the earlier callback executes. This causes my event handlers to reenter when the background thread posts additional callback(s) before the RangeChanged event is queued.
It seems like a bug to me. I understand that it's convenient for async IO completion events to be implicitly dispatched to the UI thread if the event handler was set up on the UI thread. But in UI events, everything is happening in the UI thread all the time. I am required to be in the UI thread to change the axis range and I expect the RangeChanged event to be invoked immediately in the UI thread. To Send a separate callback to the SynchronizationContext to invoke RangeChanged is not only redundant but also different than UI programming convention. It results in event handler reentrancy and partial out of order execution as shown above.
To work around this I have to find every graph, plot, axis, cursor, etc. that could be touched by my event handlers and set SynchronizeCallbacks=false. This works but is tedius and error prone to maintain.
Is there a way to cause all NI UI events to be invoked directly? It seems like it should be the default for UI code.