GC从我下面直接收集本地对象

时间:2019-07-03 22:38:50

标签: c++ .net c++-cli

我希望有人可以瞥一眼,并向我解释我所缺少的。

简短的版本是我在函数中创建一个托管对象并在其上调用成员。在该成员函数甚至还没有返回之前,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 ++代码没有做任何奇怪的事情。

  • 它正在循环处理非托管对象中的非托管变量。
  • 这不会覆盖/破坏任何内容(此代码在6-7年内一直为不受管理的客户大量使用)。
  • 我在哪里都无法使用GC或弱引用或类似方法进行任何操作。我只要创建一个本地对象,调用一个函数,它就会从我下面删除。

我确实偶然发现了一个奇怪的“修复”,但我不相信它。我认为这不是真正的解决办法,我也不明白为什么它可以解决问题:

如果我在托管的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();
}

1 个答案:

答案 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#,这毫无疑问,因为仍然可以访问其数据的对象将永远不会被垃圾收集。

https://github.com/c-tair/CSharpAgressiveGC