Safely Determining the thread managed by Excel

31 views
Skip to first unread message

Terry Aney

unread,
Mar 21, 2026, 10:17:14 AM (2 days ago) Mar 21
to Excel-DNA
Before I was always checking for thread = 1.  Now in AutoOpen, I store the Environment.CurrentManagedThreadId and use that (it seems to be 2 now).  Is this safe/correct?

Mike Sullivan

unread,
Mar 22, 2026, 10:20:18 AM (yesterday) Mar 22
to Excel-DNA
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
Reply all
Reply to author
Forward
0 new messages