Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capturing the main thread SynchronizationContext or Dispatcher from a library

I have a C# library that would like to have the ability to Send/Post work to the "main" ui thread (if one exists). This library may be used by:

  • A winforms application
  • A native application (with UI)
  • A console application (with no UI)

In the library I'd like to capture something (A SynchronizationContext, a Dispatcher, a Task Scheduler, or something else) during initialization, that will allow me to (at a later time) Send/Post work to the main thread (if the main thread has that ability--i.e. it has a message pump). For example, the library would like to put up some Winforms UI on the main thread if and only if the main application has the ability for me to get to the main thread.

Things I've tried:

  1. A SynchronizationContext: Capturing this works fine for a Winforms application (a WindowsFormsSynchronizationContext will be installed as the Current SynchronizationContext. This also works fine for the console app--since I can detect that the Current SynchronizationContext is null (and thus, know that I don't have the ability to send/post work to the main thread). The problem here is the native UI application: It has the ability (i.e. it has a message pump), but the Current Synchronization context is null and thus I can't differentiate it from the Console app case. If I could differentiate, then I could simply install a WindowsFormsSynchronizationContext on the main thread, and I'm good to go.
  2. A Dispatcher: Capturing this using Current creates a new SynchronizationContext. Thus, in all situations I will get back a Dispatcher. However, for a Console app, using Dispatcher.Invoke from a background thread will hang (as expected). I could use Dispatcher.FromThread (which doesn't create a Dispatcher for the thread if one doesn't exist). But the native UI application will return a null Dispatcher using this method, and so then I'm, again, stuck not being able to distinguish the UI application from the console application.
  3. A TaskScheduler: I could use FromCurrentSynchronizationContext. This has the same problems as the SynchronizationContext. I.e. Before calling FromCurrentSyncronizationContext, I'd have to check if the Current SynchronizationContext is null (which will be the case for the Console app and the native ui application). So, again I can't distinguish the native ui application from the console application.

I, of course, could have the user of my library specify whether or not it is a UI application when they call my Initialize method, but I was hoping to avoid that complication for the user of the library if possible.

like image 745
Matt Smith Avatar asked Jun 11 '12 13:06

Matt Smith


People also ask

What is SynchronizationContext?

SynchronizationContext is a representation of the current environment that our code is running in. That is, in an asynchronous program, when we delegate a unit of work to another thread, we capture the current environment and store it in an instance of SynchronizationContext and place it on Task object.

Does .NET core have SynchronizationContext?

NET Framework has its own SynchronizationContext , in contrast ASP.NET Core does not. That means that code running in an ASP.NET Core app by default won't see a custom SynchronizationContext , which lessens the need for ConfigureAwait(false) running in such an environment.


1 Answers

This is not in general possible, a library that's apt to be used in threads cannot make any assumptions about which particular thread is the UI thread. You can capture Synchronization.Current but that will only work correctly if your initialization method is called from the UI thread. That's not terribly unusual to work out well, like TaskScheduler.FromCurrentSynchronizationContext() tends to work by accident, but not a guarantee. You can add a check, if Thread.CurrentThread.GetApartmentState() doesn't return STA then the odds that you are not being called from the UI thread are very high. SynchronizationContext.Current will also often be null in that case, another way to check.

The (arguably) better ways are to just not worry about it and let the client code figure it out, it won't have any trouble marshaling the callback. Or to expose a property of type SynchronizationContext so that the client code can assign it. Or add it as a constructor argument. Throw an InvalidOperationException if you are ready to Post but find out that it is still null, that's an oversight that the client programmer only makes once.

like image 54
Hans Passant Avatar answered Sep 21 '22 21:09

Hans Passant