I have a native C++ application calling into a C# module, which is supposed to run it's own program loop and pass messages back to C++ through a supplied callback object, using COM. I have an existing application to work from but mine has a weird bug.
Skip to the very end for the weird behaviour and question
These C# methods are called from C++ via COM:
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("...")]
public interface IInterface
{
void Start(ICallback callback);
void Stop();
}
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("...")]
public interface ICallback
{
void Message(string message);
}
[Guid("...")]
public class MyInterface : IInterface
{
private Task task;
private CancellationTokenSource cancellation;
ICallback callback;
public void Start(ICallback callback)
{
Console.WriteLine("STARTING");
this.callback = callback;
this.cancellation = new CancellationTokenSource();
this.task = Task.Run(() => DoWork(), cancellation.Token);
Console.WriteLine("Service STARTED");
}
private void DoWork()
{
int i = 0;
while (!cancellation.IsCancellationRequested)
{
Task.Delay(1000, cancellation.Token).Wait();
Console.WriteLine("Starting iteration... {0}", i);
//callback.Message($"Message {0} reported");
Console.WriteLine("...Ending iteration {0}", i++);
}
Console.WriteLine("Service CANCELLED");
cancellation.Token.ThrowIfCancellationRequested();
}
public void Stop()
{
//cancellation.Cancel(); -- commented deliberately for testing
task.Wait();
}
In C++ I provide an implementation of ICallback, CCallback:
#import "Interfaces.tlb" named_guids
class CCallback : public ICallback
{
public:
//! \brief Constructor
CCallback()
: m_nRef(0) { }
virtual ULONG STDMETHODCALLTYPE AddRef(void);
virtual ULONG STDMETHODCALLTYPE Release(void);
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject);
virtual HRESULT __stdcall raw_Message(BSTR message)
{
std::wstringstream ss;
ss << "Received: " << message;
wcout << ss.str() << endl;
return S_OK;
}
private:
long m_nRef;
};
My C++ calling code is basically:
CCallback callback;
IInterface *pInterface = GetInterface();
cout << "Hit Enter to start" << endl;
getch();
hr = pInterface->Start(&callback);
cout << "Hit Enter to stop" << endl;
getch();
pInterface->Stop();
cout << "Hit Enter to exit" << endl;
getch();
pInterface->Stop();
This is a contrived example to avoid posting huge lumps of code but you can see the idea is the C# code is supposed to loop once a second, calling a C++ method which prints the message.
If I leave this line commented:
//callback.Message($"Message reported at {System.DateTime.Now}"); it works exactly as one would imagine. If I uncomment it then what happens is:
CCallback callback;
IInterface *pInterface = GetInterface();
cout << "Hit Enter to start" << endl;
getch();
hr = pInterface->Start(&callback);
STARTING
Starting iteration... 0
cout << "Hit Enter to stop" << endl;
getch();
pInterface->Stop();
Received: Message 0 reported
...Ending iteration 0
Starting iteration... 1
Received: Message 1 reported
...Ending iteration 1
(... and so on.)
cout << "Hit Enter to exit" << endl;
getch();
return;
Conclusion
So for some reason the call callback.Message is stalling my Task, until Task.Wait is called. Why on earth would this be? How does it get stuck and how does waiting on the task release it? My assumption is the threading model via COM means I have some sort of deadlock but can anyone be more specific?
I personally think running this all in a dedicated Thread is better but it's how an existing application works so I'm just really curious what is happening.
UPDATE
So I tested new Thread(DoWork).Start() Vs Task.Run(()=>DoWork()) and I get the exact same behaviour - it stalls now until Stop calls Thread.Join.
So I'm thinking COM for some reason is suspending the entire CLR or something along those lines.