Hi Govert,
I've been playing with ways to improve my code. I made the following static helper, which I can "using static" reference easily, to return to the STA thread context upon continuation.
```
namespace ExcelDnaQueueTool.statics;
static class ExcelDnaQueueTool
{
public static ExcelAwaitable SwitchToExcelSTA() => new ExcelAwaitable();
public readonly struct ExcelAwaitable : INotifyCompletion
{
readonly string _stack;
public ExcelAwaitable(string stack) => _stack = stack;
public ExcelAwaitable GetAwaiter() => new(Environment.StackTrace);
public bool IsCompleted => false;
public void OnCompleted(Action continuation)
{
var stack = _stack;
QueueAsMacroAsync(() =>
{
try
{
continuation();
}
catch
{
// Log the stack trace (we lose the caller if we don't capture the stack in this struct
LogError(stack);
throw;
}
});
}
public void GetResult() { }
}
public static Task QueueAsMacroAsync(Action action)
{
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var stack = Environment.StackTrace;
try
{
ExcelAsyncUtil.QueueAsMacro(() =>
{
try
{
action();
tcs.SetResult();
}
catch (Exception ex)
{
tcs.SetException(ex);
}
});
}
// The only exceptions that QueueAsMacro throws are NullRefs
catch (NullReferenceException ex)
{
tcs.SetException(new InvalidOperationException(
$"QueueAsMacro<Action> failed: macro queue unavailable (possibly called from STA inside an event?), stack={stack}", ex));
}
return tcs.Task;
}
}
```
So in a button handler in .NET I can do something like:
```
async void ButtonClicked(IRibbonControl _)
{
var data = await _fetchDataFromApi();
// returned on a background thread
await SwitchToExcelSTA();
// back on STA thread
// write table to sheet
}
```
It seems to work reasonably well. There are two mechanisms there, 1 - the QueueAsMacroAsync task, allowing you to await the macro to be finished, and 2 the SwitchToExcelSTA awaitable allowing a nice async code flow.
Can anyone foresee any problems with the either the QueueAsMacroAsync method or the SwitchToExcelSTA pattern?
1. I'm seeing exceptions when trying to queue macros, where it's throwing a null ref. The only thing I can find suggests that the macro queue might not be initialized yet? That might make sense during the initial OnConnection perhaps, but I'm seeing cases where I queue a UI action well after it has initialized and is being used, which succeeds, and then later I start hitting these null refs and I can no longer queue anything successfully from then on. I'm wondering if I should revert the above await SwitchToExcelSTA(), or if I should even revert my use of QueueAsMacroAsync. How else do you await macros to be finished?
2. If I await a task to read data from a web API, I notice that sometimes it continues/resumes on the STA thread. It seems maybe sporadic? I'm wondering if sometimes when I queue a macro it fails because I'm queueing it from the STA thread. Is there a good way to know if I'm already on the STA context?
I tried using .ConfigureAwait(false) but I understand that doesn't guarantee you won't continue on the original (STA) context. For awaiting short disk-bound i/o it returns so quickly that it's still on the STA thread it seems. For longer awaits to web API's it seems it returns mostly on background threads, but can I be sure of that? When can I be sure I'm on the STA thread or not?
Thanks for your consideration
Stu