Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Parallel.For execute the WinForms message pump, and how to prevent it?

I'm trying to speed up a lengthy (a few ms) operation* using Parallel.For, but I'm getting Paint events all over my WinForms application before the method has returned - suggesting it somehow triggers a message pump. The overall redraw, however, leads to accessing data in an inconsistent state, producing erratic errors and exceptions. I need to assure that Parallel.For, while blocking, doesn't trigger UI code.

My research on this so far has been inconclusive and pointed me roughly to things like synchronization contexts and TaskScheduler implementations, but I have yet to make sense of it all.

If someone could help me along the way by clearing some things up, that would very much be appreciated.

  1. What is the chain of events that leads to Parallel.For triggering WinForms message pump?
  2. Is there any way I can prevent this from happening entirely?
  3. Alternatively, is there any way to tell if a UI event handler is called from the regular message pump, or the "busy" message pump as triggered by Parallel.For?

Edit: * Some context: The above few ms operation is part of a game engine loop, where 16 ms are available for a full update - hence the attribute "lengthy". The context of this problem is executing a game engine core inside its editor, which is a WinForms application. Parallel.For happens during the internal engine update.

like image 385
Adam Avatar asked Feb 21 '16 11:02

Adam


1 Answers

This comes from the CLR, it implements the contract that an STA thread (aka UI thread) is never allowed to block on a synchronization object. Like Parallel.For() does. It pumps to ensure that no deadlock can occur.

This gets Paint events to fire, and some others, the exact message filtering is a well-kept secret. It is pretty similar to DoEvents() but the stuff that is likely to cause re-entrancy bugs blocked. Like user input.

But clearly you have a DoEvents() style bug in spades, re-entrancy is forever a nasty bug generator. I suspect you'll need to just set a bool flag to ensure that the Paint event skips an update, simplest workaround. Changing the [STAThread] attribute on the Main() method in Program.cs to [MTAThread] is also a simple fix, but is quite risky if you also have normal UI. Favor the private bool ReadyToPaint; approach, it is simplest to reason through.

You should however investigate exactly why Winforms thinks that Paint is needed, it shouldn't since you are in control over the Invalidate() call in a game loop. It may fire because of user interactions, like min/max/restoring the window but that should be rare. Non-zero odds that there's another bug hidden under the floor mat.

like image 189
Hans Passant Avatar answered Sep 25 '22 18:09

Hans Passant