尝试在包装 C++ Com 对象的 C# 代码中使用 RCW 时,我们遇到了死锁。它在大多数情况下都有效,但有时我们会在主线程停止的地方出现死锁:
Child SP IP Call Site
0019ad34 75002b3c [GCFrame: 0019ad34]
0019ad50 75002b3c [HelperMethodFrame: 0019ad50] System.Runtime.InteropServices.Marshal.InternalWrapIUnknownWithComObject(IntPtr)
0019adcc 170de1d5 .ReadAppParameter(IDispatch*)
0019adf0 170ddd4a DomainBoundILStubClass.IL_STUB_ReversePInvoke(Int32)
我们已经尝试调试它,但这真的很难。不知何故,有些东西阻塞了在托管和本机之间进行通信的线程,但我看不出是哪个。
调用 !eestack 为我提供了以下信息,但我看不到导致死锁的原因。这个线程正在调用 CleanupWrappersInCurrentCtxThread 这有点可疑,但我没有关于这一切在内部如何工作的详细信息:
Thread 0
Current frame: win32u!NtUserPeekMessage+0xc
ChildEBP RetAddr Caller, Callee
0019a2bc 7530fb5e user32!_PeekMessage+0x2e, calling win32u!NtUserPeekMessage
0019a2f8 75305a58 user32!DispatchMessageWorker+0x328, calling win32u!NtUserDispatchMessage
0019a310 752f6b7c user32!PeekMessageA+0x19c, calling user32!_PeekMessage
0019a34c 698040b8 msvbvm60!VBDllUnRegisterServer+0x94c5
0019a3a0 763182a9 combase!CCliModalLoop::HandlePendingMessage+0xe2
0019a3c8 75002b4c win32u!NtUserCallOneParam+0xc
0019a3cc 75311182 user32!GetQueueStatus+0x52, calling win32u!NtUserCallOneParam
0019a3e0 76318149 combase!CCliModalLoop::HandleWakeForMsg+0x126, calling combase!CCliModalLoop::HandlePendingMessage
0019a3f8 7631838e combase!CCliModalLoop::MyPeekMessage+0x43, calling combase!__SEH_epilog4
0019a3fc 76318328 combase!CCliModalLoop::PeekRPCAndDDEMessage+0x31, calling combase!CCliModalLoop::MyPeekMessage
0019a408 753112cd user32!MsgWaitForMultipleObjectsEx+0x4d, calling user32!RealMsgWaitForMultipleObjectsEx
0019a440 7629de9a combase!CCliModalLoop::BlockFn+0x234, calling combase!CCliModalLoop::HandleWakeForMsg
0019a474 76321094 combase!ClassicSTAThreadWaitForHandles+0xb4, calling combase!CCliModalLoop::BlockFn
0019a534 762f1339 combase!CoWaitForMultipleHandles+0x79, calling combase!ClassicSTAThreadWaitForHandles
0019a560 737eb78e clr!MsgWaitHelper+0x64, calling combase!CoWaitForMultipleHandles
0019a5b4 737eb71e clr!Thread::DoAppropriateWaitWorker+0x1d8, calling clr!MsgWaitHelper
0019a638 7393ce6e clr!Thread::DoAppropriateWait+0x64, calling clr!Thread::DoAppropriateWaitWorker
0019a6a4 738b310e clr!Thread::JoinEx+0xc5, calling clr!Thread::DoAppropriateWait
0019a6f4 7394a3a6 clr!RCWCleanupList::CleanupWrappersInCurrentCtxThread+0x133, calling clr!Thread::JoinEx
0019a718 737daee3 clr!SyncBlock::GetInteropInfo+0x38, calling ntdll!RtlInterlockedPopEntrySList
0019a71c 737a37bb clr!SyncBlock::GetInteropInfo+0xbd, calling clr!_EH_epilog3
0019a748 737e5091 clr!RCW::Initialize+0x259, calling clr!RCWCleanupList::CleanupWrappersInCurrentCtxThread
0019a794 75315d0b user32!_InternalCallWinProc+0x2b
0019a7d4 75306830 user32!UserCallWinProcCheckWow+0x3d0, calling user32!UserCallWinProcCheckWow+0x62e
0019a828 13433195 *** WARNING: Unable to verify checksum for ociliba.dll
ociliba!OCI_ErrorGet+0x55, calling *** WARNING: Unable to verify checksum for oci.dll
oci!OCIThreadKeyGet
0019a844 13446788 ociliba!OCI_MutexRelease+0x78, calling oci!OCIThreadMutexRelease
0019a85c 13466059 ociliba!OCI_StringDuplicateFromOracleString+0xc9, calling ociliba!memcpy
0019a878 1341f355 ociliba!OCI_ColumnDescribe+0xa15, calling oci!OCIDescriptorFree
0019a88c 1341f376 ociliba!OCI_ColumnDescribe+0xa36, calling ociliba!__security_check_cookie
0019a8c0 7717ae60 ntdll!RtlFreeHeap+0x1e0, calling ntdll!RtlpHpStackLoggingEnabled
0019a908 747e5fcb ucrtbase!_free_base+0x1b, calling ntdll!RtlFreeHeap
0019a91c 747e5f98 ucrtbase!free+0x18, calling ucrtbase!_free_base
0019a92c 601fa425 *** WARNING: Unable to verify checksum for NC3001_28.dll
NC3001_28!operator delete+0xb, calling ucrtbase!free
0019a938 60114bb0 NC3001_28!std::_Deallocate<8,0>+0x50, calling NC3001_28!operator delete
0019a958 6015df27 NC3001_28!std::_Tgt_state_t<std::_String_const_iterator<std::_String_val<std::_Simple_types<char> > > >::~_Tgt_state_t<std::_String_const_iterator<std::_String_val<std::_Simple_types<char> > > >+0x87, calling NC3001_28!std::_Deallocate<8,0>
0019a980 60162c3d NC3001_28!std::_Matcher<std::_String_const_iterator<std::_String_val<std::_Simple_types<char> > >,char,std::regex_traits<char>,std::_String_const_iterator<std::_String_val<std::_Simple_types<char> > > >::_Do_if+0x14d, calling NC3001_28!std::_Tgt_state_t<std::_String_const_iterator<std::_String_val<std::_Simple_types<char> > > >::~_Tgt_state_t<std::_String_const_iterator<std::_String_val<std::_Simple_types<char> > > >
0019a9a8 7717ae60 ntdll!RtlFreeHeap+0x1e0, calling ntdll!RtlpHpStackLoggingEnabled
0019a9f0 747e5fcb ucrtbase!_free_base+0x1b, calling ntdll!RtlFreeHeap
0019aa04 747e5f98 ucrtbase!free+0x18, calling ucrtbase!_free_base
0019aa14 601fa425 NC3001_28!operator delete+0xb, calling ucrtbase!free
0019aa20 60114bb0 NC3001_28!std::_Deallocate<8,0>+0x50, calling NC3001_28!operator delete
0019aa40 6015df27 NC3001_28!std::_Tgt_state_t<std::_String_const_iterator<std::_String_val<std::_Simple_types<char> > > >::~_Tgt_state_t<std::_String_const_iterator<std::_String_val<std::_Simple_types<char> > > >+0x87, calling NC3001_28!std::_Deallocate<8,0>
0019aa68 6015b506 NC3001_28!std::_Regex_search2<std::_String_const_iterator<std::_String_val<std::_Simple_types<char> > >,std::allocator<std::sub_match<std::_String_const_iterator<std::_String_val<std::_Simple_types<char> > > > >,char,std::regex_traits<char>,std::_String_const_iterator<std::_String_val<std::_Simple_types<char> > > >+0x1b6, calling NC3001_28!std::_Tgt_state_t<std::_String_const_iterator<std::_String_val<std::_Simple_types<char> > > >::~_Tgt_state_t<std::_String_const_iterator<std::_String_val<std::_Simple_types<char> > > >
0019aa70 7717ae60 ntdll!RtlFreeHeap+0x1e0, calling ntdll!RtlpHpStackLoggingEnabled
0019aa80 7717ae60 ntdll!RtlFreeHeap+0x1e0, calling ntdll!RtlpHpStackLoggingEnabled
0019aa90 7717ae60 ntdll!RtlFreeHeap+0x1e0, calling ntdll!RtlpHpStackLoggingEnabled
0019aad4 76eedcbe KERNELBASE!wil_details_ModifyFeatureData+0x2d, calling KERNELBASE!wil_details_SetPropertyFlagCallback
0019ab50 7717d35e ntdll!RtlAllocateHeap+0x3e, calling ntdll!RtlpAllocateHeapInternal
0019ab58 7717d35e ntdll!RtlAllocateHeap+0x3e, calling ntdll!RtlpAllocateHeapInternal
0019ab70 737a2788 clr!EEHeapAlloc+0x2c, calling ntdll!RtlAllocateHeap
0019ab88 737a27ca clr!EEHeapAllocInProcessHeap+0x5b, calling clr!EEHeapAlloc
0019ab94 737a2767 clr!ClrAllocInProcessHeap+0x23
0019abb8 737e514e clr!RCW::CreateRCWInternal+0xdc, calling clr!RCW::Initialize
终结器看起来不错:
Thread 2
Current frame: ntdll!NtWaitForMultipleObjects+0xc
ChildEBP RetAddr Caller, Callee
050cfb78 76ee9d13 KERNELBASE!WaitForMultipleObjectsEx+0x133, calling ntdll!NtWaitForMultipleObjects
050cfbd0 76ee8409 KERNELBASE!WaitForSingleObjectEx+0x99, calling ntdll!NtWaitForSingleObject
050cfc28 7394821e clr!RCW::EnterContext+0x3a, calling clr!CtxEntry::EnterContext
050cfc44 737a1580 clr!CLREventWaitHelper2+0x33, calling KERNELBASE!WaitForSingleObjectEx
050cfc50 737a159d clr!CLREventWaitHelper2+0x4e, calling clr!_EH_epilog3
050cfc70 737a159d clr!CLREventWaitHelper2+0x4e, calling clr!_EH_epilog3
COM 对象用于获取 Oracle DB 句柄,选择是在 C# 上完成的。我们可以看到预言机参与其中,但我们仍然不知道如何检查谁真正在阻止。
如果有人对我如何进行或知道 RCW 内部如何工作以及可能导致僵局的原因有任何想法,我将不胜感激。
现在,我们生成了一个调用 RCW 的线程,它似乎可以工作,但实际上不应该发生这种死锁。
** 编辑 02.10.21 ** 这就是我的解决方法的样子:
void ParameterReaderHelper::ReadAppParameter(Object^ obj)
{
auto lp = (IntPtr)obj;
MyInterface^ myComInterface = nullptr;
try
{
System::Diagnostics::Debug::WriteLine(
"ThreadState: {0},
ApartmentState: {1}",
Threading::Thread::CurrentThread->ThreadState,
Threading::Thread::CurrentThread->GetApartmentState());
myComInterface = (MyInterface^)EnterpriseServicesHelper::WrapIUnknownWithComObject(lp);
System::Diagnostics::Debug::WriteLine("myComInterface:" + myComInterface->GetHashCode());
// do something with myComInterface
}
finally
{
// ...
}
}
我用这个来称呼它……虽然我并不为此感到自豪。
bool ReadAppParameter(IDispatch* lp)
{
const int Delay = 100;
const int OneMinute = 60 * 1000 / Delay;
System::Diagnostics::Debug::WriteLine(
"ThreadState: {0},
ApartmentState: {1}",
Threading::Thread::CurrentThread->ThreadState,
Threading::Thread::CurrentThread->GetApartmentState());
auto obj = IntPtr(lp);
auto goh = gcnew ParameterReaderHelper();
Threading::Tasks::Task^ myTask = Threading::Tasks::Task::Factory->StartNew((Action<Object^>^)(gcnew Action<Object^>(goh, &ParameterReaderHelper::ReadAppParameter)), obj);
int count = 0;
while (!goh->IsFinished() && count < OneMinute )
{
Threading::Thread::Sleep(Delay);
count++;
}
if (count >= OneMinute)
{
throw gcnew System::InvalidOperationException("blabla");
}
return true;
}
但这暂时解决了阻塞问题。如果我尝试等待任务。它永远阻塞。
敲响任何铃铛?
谢谢。