Search code examples
c#asp.net-corecompinvokesta

C++ DLL in ASP.NET Core STA thread not invoking callback


I need to control a camera from within an ASP.NET core api and all communication is via a pInvoke dll. In the docs it explicitly states

To create a user thread and access the camera from that thread, be sure to execute CoInitializeEx( NULL, COINIT_APARTMENTTHREADED ) at the start of the thread and CoUnInitialize() at the end.

e.g

CoInitializeEx( NULL, COINIT_APARTMENTTHREADED );
EdsSendCommand(camera, kEdsCameraCommand_TakePicture, 0);
CoUninitialize()

My camera service works from a winforms application (STA) however when I move it over to my API, the callback does not fire when events happen. I've tried wrapping the component in an STA thread and setting up an execution loop but callbacks still do not fire.

I think I might need a message pump but am unsure exactly how this should work.

Non working code:

Thread handlerThread;
handlerThread = new Thread(STAThreadLoop);
handlerThread.SetApartmentState(ApartmentState.STA);
handlerThread.Start();

and in the thread loop

void STAThreadLoop()
{
    logger.LogInformation("Starting STAThreadLoop...");
    lock (handlerThreadLock)
    {
        handlerSignal.Set();
        while (!exitHandlerThreadLoop)
        {
            Thread.Yield();
            Monitor.Wait(handlerThreadLock);
            if (handlerThreadAction != null)
            {
                try
                {
                    handlerThreadAction();
                }
                catch (Exception ex)
                {
                    logger.LogError(ex, "Error executing action on STA thread: {ThreadName}", Thread.CurrentThread.Name);
                }
            }
            Monitor.Pulse(handlerThreadLock);
        }
    }
}

and then to create the component

RunSTAAction(() =>
{
    handler = new SDKHandler(loggerFactory.CreateLogger<SDKHandler>());
});

and the method to transition to the STA thread

void RunSTAAction(Action action)
{
    if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
    {
        lock (handlerThreadLock)
        {
            handlerThreadAction = action;
            Monitor.Pulse(handlerThreadLock);
            Monitor.Wait(handlerThreadLock);
        }
    }
    else
    {
        action();
    }
}

Update: This is actually fixed, see answer below


Solution

  • I found a way to do this using the excellent answer by Noseratio in this question: StaTaskScheduler and STA thread message pumping

    Effectively, we create an instance of the ThreadAffinityTaskScheduler and pass the WaitHelpers.WaitWithMessageLoop as a wait function.

    ThreadAffinityTaskScheduler messageScheduler;
    messageScheduler = new ThreadAffinityTaskScheduler(3, staThreads: true, waitHelper: WaitHelpers.WaitWithMessageLoop);
    messageScheduler.Run(new Action(STAThreadLoop), CancellationToken.None);