GC собирает местный объект прямо из-под меня - PullRequest
0 голосов
/ 04 июля 2019

Я надеюсь, что кто-то может взглянуть на это и объяснить мне, что мне не хватает.

Краткая версия: я создаю управляемый объект в функции и вызываю его члена. До того, как эта функция-член вернулась, GC завершает объект прямо из-под меня из другого потока.

Вот код C #, который создает объект и вызывает функцию C ++ / CLI

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 или слабыми ссылками или чем-то подобным Я просто создаю локальный объект, вызываю функцию, и она удаляется из-под меня.

Я наткнулся на странное "исправление", но я ему не доверяю. Я не думаю, что это действительно исправление, и я не понимаю, почему это предотвращает проблему:

Если я поместил фрейм try / catch в управляемую функцию C ++ / CLI IntegrateNormalMap (чтобы преобразовать любые неуправляемые исключения 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());
    }
}

Примечание. Хотя это, очевидно, хорошая общая практика (т.е. предотвращение выхода неуправляемых исключений), лежащий в основе неуправляемый код фактически не выдает никаких исключений ни в одном из случаев. Это все еще обрабатывает.

Я также проверил, что я * компилирую этот класс C ++ / CLI с параметром / clr. Управляется.

Так что теперь я запутался. Действительно ли моя структура исключений "устранила" проблему? Если так, что это было и как этот простой шаг исправляет это?

(я использую Visual Studio 2019 и .NET Framework 4.8)

РЕДАКТИРОВАТЬ: Просто добавьте, чтобы показать деструктор и финализатор моего класса C ++ / CLI Integrator, чтобы уточнить комментарии Ханса Пассанта ниже.

// 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();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...