How to execute pending Control.Invoke or WindowsFormsSynchronizationContext.Send requests

Disclaimer. This is perhaps not a best way to do, but I couldn’t find any better way.

Let’s imagine that you have some Task to run in the background. And that task has to update UI for example progress bar. Or that task has to call some callbacks in context of main thread. For firs case you would use Invoke, for second case you would use SynchronizationContext.Send. Implementation is very simple. WinForms will add request to queue of requests and post message to main thread and then it wait on request handle to be signaled. Main thread receives message, processes request and signal on request event. Very simple and quite basic. Post is the same as Send but calling thread will not wait.

But imagine that main thread is also busy doing something. In this case these requests will patiently wait to be processed. But in cases it will be way better if main thread processes them (and progress bar will be updated) or in some cases main thread could wait on background thread to finish something. But background thread sent Invoke request and waiting on its completion. As result there is deadlock. Usually it is indication of bad design, but in some cases, there is nothing else you can do.

One way to solve this problem will be to process messages. But you should never do so. This just opens huge box of problems. Because processing messages creates reentrancy to your application. Imagine you have button Calcualate and user pressed it and your code started what I explained above. Then during wait for background thread and processing messages, user can press this button again. Or can close application or resize it or do something else. And then you will start receiving crash reports with really strange things and only later to realize that now you have two calculation running in parallel when your code does not support this scenario. You can try to fix this problem, but later you will find new one, even more complex to reproduce. Trust me, there should be only few places where messages are processed: main message loop and dialog message loops. That’s it.

But how else can I solve it? Well after analyzing source code I found that if I call Invoke or Send from main thread, new request is still added to queue and then process all of them. Bingo. It is a bit quirky as new request will be created each time, but it is only way I found without messing with private functions and fields. You can check Control. MarshaledInvoke and InvokeMarshaledCallbacks. It is good idea to create delegate upfront and do not create it each time and then use something like this:
SynchronizationContext.Current?.Send(empty, null) or similar with Control.Invoke

Comments

Post comment