我希望有人可以瞥一眼,并向我解释我所缺少的。
简短的版本是我在函数中创建一个托管对象并在其上调用成员。在该成员函数甚至还没有返回之前,GC就从另一个线程中从我下面直接完成该对象的定型。
这是创建对象并调用C ++ / CLI函数的C#代码
var integrator = Integrator.Create();
_logger.Debug("Integrating normal map. Res=" + model.ResolutionMmpp);
var hm = integrator.IntegrateNormalMap(nm, model.ResolutionMmpp);
// *** 'integrator' gets finalized before I even get here. How? ***
_logger.Debug("Back from integrate");
这是IntegrateNormalMap
(C ++ / CLI代码)的实现。它调用其非托管等效C ++代码。
HeightMap^ Integrator::IntegrateNormalMap(NormalMap^ nrm, double res)
{
// Note: 'm_psi' is a pointer to the valid, unmanaged C++ object
return gcnew HeightMap(m_psi->integrateNormalMap(nrm->sdkMap(), res));
}
我在Integrator
类终结器(即Integrator::!Integrator
)上设置了一个断点,并且可以看到垃圾回收器从不同的线程调用C ++ / CLI对象的终结器。
这是正在调用的终结器的调用堆栈
Sdk::Integrator::~Integrator() Line 26 C++
Sdk::Integrator::Dispose() C++
Sdk::Integrator::Dispose() C++
Sdk::Integrator::!Integrator() Line 38 C++
Sdk::Integrator::Dispose() C++
[Native to Managed Transition]
00007ffeee324034() Unknown
00007ffeee473691() Unknown
但是与此同时,IntegrateNormalMap()
函数仍在原始线程上运行。
gs::detail::PoissonIntegratorV2014::reconstructNormals(normals={...}) Line 79 C++
gs::detail::PoissonIntegratorV2014::integrateNormalMap(nrm, res) Line 35 C++
[Managed to Native Transition]
Sdk::Integrator::IntegrateNormalMap(nrm, res) Line 45 C++
Mobile.ViewModels.ScanVm.Generate3d(ffcEnum, token) Line 595 C#
Mobile.ViewModels.ScanVm.Generate3d(token) Line 642 C#
Capture.ViewModels.NormalScanJob.Generate3d() Line 60 C#
Capture.ViewModels.CaptureVm.get_Mesh.AnonymousMethod__27_4(job = {Capture.ViewModels.NormalScanJob}) Line 371 C#
System.Threading.Tasks.Dataflow.TransformBlock<Capture.ViewModels.NormalScanJob, Capture.ViewModels.NormalScanJob>.ProcessMessage(transform, messageWithId = {[{Capture.ViewModels.NormalScanJob}, 0]}) Unknown
System.Threading.Tasks.Dataflow.TransformBlock<System.__Canon, System.__Canon>..ctor.AnonymousMethod__3(messageWithId) Unknown
System.Threading.Tasks.Dataflow.Internal.TargetCore<Capture.ViewModels.NormalScanJob>.ProcessMessagesLoopCore() Unknown
System.Threading.Tasks.Task.Execute() Unknown
System.Threading.ExecutionContext.RunInternal(executionContext, callback, state, preserveSyncCtx) Unknown
System.Threading.ExecutionContext.Run(executionContext, callback, state, preserveSyncCtx) Unknown
System.Threading.Tasks.Task.ExecuteWithThreadLocal(currentTaskSlot = Id = 2494, Status = Running, Method = Inspecting the state of an object in the debuggee of type System.Delegate is not supported in this context.) Unknown
System.Threading.Tasks.Task.ExecuteEntry(bPreventDoubleExecution) Unknown
System.Threading.ThreadPoolWorkQueue.Dispatch() Unknown
[Native to Managed Transition]
00007ffeee324034() Unknown
00007ffeee473691() Unknown
请注意,非托管C ++代码没有做任何奇怪的事情。
我确实偶然发现了一个奇怪的“修复”,但我不相信它。我认为这不是真正的解决办法,我也不明白为什么它可以解决问题:
如果我在托管的C ++ / CLI IntegrateNormalMap
函数中放置一个try / catch框架(以使其将任何非托管的C ++异常转换为托管的异常),则问题将消失。在这里,用try / catch重写
HeightMap^ Integrator::IntegrateNormalMap(NormalMap^ nrm, double res)
{
try
{
return gcnew HeightMap(m_psi->integrateNormalMap(nrm->sdkMap(), res));
}
catch (const std::exception& ex)
{
throw gcnew SdkException(ex.what());
}
}
注意:尽管这显然是一种很好的通用做法(即防止非托管异常转义),但在两种情况下,底层非托管代码实际上都不会引发任何异常。它仍在处理中。
我还验证了我 am 使用/ clr选项编译此C ++ / CLI类。它是受管理的。
所以现在我感到困惑。我的异常框架是否真正“解决”了问题?如果是这样,那是什么?该简单步骤如何解决?
(我正在使用Visual Studio 2019和.NET Framework 4.8)
编辑:只是为了显示我的C ++ / CLI Integrator类的析构函数和终结器,以澄清下面的Hans Passant的评论。
// Take ownership of the given unmanaged pointer.
Integrator::Integrator(std::unique_ptr<gs::Integrator> sip)
: m_psi(sip.release())
{
}
Integrator::~Integrator()
{
// Clean up and null out in case we get called twice
delete m_psi;
m_psi = nullptr;
}
Integrator::!Integrator()
{
this->~Integrator();
}
答案 0 :(得分:0)
var integrator = Integrator.Create();
_logger.Debug("Integrating normal map. Res=" + model.ResolutionMmpp);
var hm = integrator.IntegrateNormalMap(nm, model.ResolutionMmpp);
// *** 'integrator' gets finalized before I even get here. How? ***
_logger.Debug("Back from integrate");
集成商”在我到达这里之前就已完成。怎么样?
此后,不再使用变量积分器,因此可以免费进行垃圾回收。在下面的调用中访问了它的成员m_psi之后,实际上已经是对的,因为这是对“ this”的最后引用。
HeightMap^ Integrator::IntegrateNormalMap(NormalMap^ nrm, double res)
{
// Note: 'm_psi' is a pointer to the valid, unmanaged C++ object
return gcnew HeightMap(m_psi->integrateNormalMap(nrm->sdkMap(), res));
}
正如您指出的那样,您在集成商的终结器中删除m_psi。 为了防止这种情况,您只需要防止在调用结束之前就对集成器进行垃圾回收。例如:在调用后将其设置为字段或使用GC.KeepAlive(integrator);
关于.Net 4.8 / 4.7.2: [C#] 我可以确认,这是4.8中的新错误/功能。我编写了一个示例应用程序来重现该行为。在4.7.2中,该对象保持活动状态;在4.8中,该对象在方法调用期间处于GC状态。 然后,一旦不再引用对象,即使它的一种方法仍在运行,我也将用纯C#再现相同的行为,并得到相同的结果。对于C#,这毫无疑问,因为仍然可以访问其数据的对象将永远不会被垃圾收集。