gpt4 book ai didi

c# - 为什么我的 C# 代码在回调到 C++ COM 直到 Task.Wait/Thread.Join 时停止?

转载 作者:行者123 更新时间:2023-12-01 14:48:39 25 4
gpt4 key购买 nike

我有一个调用 C# 模块的 native C++ 应用程序,该模块应该运行它自己的程序循环并使用 COM 通过提供的回调对象将消息传递回 C++。我有一个现有的应用程序可以使用,但我的有一个奇怪的错误。

对于奇怪的行为和问题,跳到最后

这些 C# 方法通过 COM 从 C++ 调用:

[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();
}

在 C++ 中,我提供了 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;
};

我的 C++ 调用代码基本上是:
    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();

这是一个人为的示例,以避免发布大量代码,但您可以看到这个想法是 C# 代码应该每秒循环一次,调用打印消息的 C++ 方法。

如果我离开这一行评论: //callback.Message($"Message reported at {System.DateTime.Now}");它的工作原理与人们想象的完全一样。如果我取消注释,那么会发生什么:
    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



(... 等等。)
    cout << "Hit Enter to exit" << endl;
getch();
return;

结论

所以出于某种原因来电 callback.Message正在拖延我的 Task , 直到 Task.Wait叫做。为什么会这样?它是如何卡住的,等待任务如何释放它?我的假设是通过 COM 的线程模型意味着我有某种死锁,但谁能更具体?

我个人认为在专用 Thread 中运行这一切更好,但这是现有应用程序的工作方式,所以我真的很好奇发生了什么。

更新

所以我测试了 new Thread(DoWork).Start()对比 Task.Run(()=>DoWork())我得到了完全相同的行为 - 它现在停止直到 Stop来电 Thread.Join .

所以我认为 COM 出于某种原因正在暂停整个 CLR 或类似的东西。

最佳答案

这听起来像:

  • 您的回调实现对象在 STA 单元线程(主线程)上实例化。
  • 该任务在单独的线程上运行,即 STA 或 MTA。
  • 来自后台线程的接口(interface)调用被编码(marshal)回主线程。
  • 您的主线程中没有消息泵。
  • task.Wait被调用时,它会运行一个循环,允许主线程处理 COM 调用。

  • 您可以通过检查以下内容来验证这一点:
  • 您应该调用 CoInitializeEx在您的 C++ 客户端应用程序的主线程中显式地显示。检查您在那里使用的线程模型。如果您不调用它,请添加它。添加它可以解决您的问题吗?我希望不会,但如果确实如此,这意味着 COM 和 .NET 之间存在一些交互,这可能是设计使然,但会让您感到困惑。
  • 添加日志或设置调试器,以便您可以查看哪些线程正在执行哪些代码。您的代码应该只在两个线程中运行——您的主线程和一个后台线程。当您重现问题状况时,我相信您会看到 Message()在主线程上调用方法实现。
  • 用 Windows 应用程序替换您的控制台应用程序,或者只是在您的控制台应用程序中运行消息泵。我相信您会看到没有发生挂起。

  • 我也猜到了为什么 Task.WaitThread.Join似乎解除了对调用的阻止,以及为什么当您在较大的应用程序中看不到此问题时,您可能会在精简的用例中看到此问题。

    在 Windows 中等待是一个有趣的野兽。本能地,我们想象 Task.WaitThread.Join将完全阻塞线程,直到满足等待条件。有 Win32 函数(例如 WaitForSingleObject )可以做到这一点,还有像 getch 这样的简单 I/O 操作。也这样做。但也有 Win32 函数允许在等待时运行其他操作(例如 WaitForSingleObjectEx bAlertable 设置为 TRUE )。在 STA 中,COM 和 .NET 使用最复杂的等待函数 CoWaitForMultipleHandles ,它运行一个处理调用 STA 的传入消息的 COM 模式循环。当您调用此函数或任何使用它的函数时,可以在满足等待条件或函数返回之前执行任意数量的传入 COM 调用和/或 APC 回调。 (旁白:当您从一个线程对不同单元中的 COM 对象进行 COM 调用时也是如此——来自其他单元的调用者可以在调用线程的函数调用返回之前调用调用线程。)

    至于为什么你没有在完整的应用程序中看到它,我猜你减少用例的简单性实际上给你带来了更多的痛苦。完整的应用程序可能有等待、消息泵、跨线程调用或其他一些最终允许 COM 调用在足够的时间通过的东西。如果完整的应用程序是 .NET,您应该知道与 COM 的互操作对于 .NET 来说是非常基础的,因此您不必直接做某事可能会让 COM 调用通过。

    关于c# - 为什么我的 C# 代码在回调到 C++ COM 直到 Task.Wait/Thread.Join 时停止?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60411617/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com