Sharing the approach I have been using. I cannot honestly tell if it technically correct and doesn't have some odd failure scenario, but it seems to work for my use case.
It is based on three findings:
< 1 > If you provide an explicit initializer (e.g., [ThreadStatic] static bool myBool = true;), only the first thread (the one that initializes the class) will see the value as true. All other threads will see the default value for a bool, which is false.
...define:
[ThreadStatic]
public static readonly bool IsMainThread = true;>> This means you can determine if you are on the main thread (the one that first initializes the class) by testing: if (MyApp.IsMainThread)...
>> Also, you can capture the UI context using WindowsFormsSynchronizationContext (referenced somewhere in the ExcelDNA docs), so:
...define:
internal static SynchronizationContext? _uiContext;...and in the class initialization:
if (_uiContext == null)
_uiContext = new WindowsFormsSynchronizationContext();>> With supporting classes based on this article, we can easily switch between threads by simply await-ing it:
https://thomaslevesque.com/2015/11/11/explicitly-switch-to-the-ui-thread-in-an-async-method/...so in code, where needed, if we are not on the main thread, wait for it and switch to it:
if (!MyApp.IsMainThread) await MyApp._uiContext;>> supporting class
internal static class AsyncHelpers
{
// https://thomaslevesque.com/2015/11/11/explicitly-switch-to-the-ui-thread-in-an-async-method/
public static SynchronizationContextAwaiter GetAwaiter(this SynchronizationContext context)
{
return new SynchronizationContextAwaiter(context);
}
}
public struct SynchronizationContextAwaiter : INotifyCompletion
{
private static readonly SendOrPostCallback _postCallback = state => ((Action)state)();
private readonly SynchronizationContext _context;
public SynchronizationContextAwaiter(SynchronizationContext context)
{
_context = context;
}
public bool IsCompleted => _context == SynchronizationContext.Current;
public void OnCompleted(Action continuation) => _context.Post(_postCallback, continuation);
public void GetResult() { }
}If this is technically flawed in some way, or subject to some nasty race condition, feel free to comment. I am just sharing because I thought it was a simple solution to a common problem.
Mike