In a program that uses DirectX to render a 3D component I use a dedicated rendering thread that apparently gets created if I call DispatcherQueueController::CreateOnDedicatedThread(). What needs to be rendered is influenced by actions done on the main thread which is why I use a scoped_lock to synchronize the access.
The code that creates the rendering thread and the code that runs within it looks as follows:
void MyCustomView::StartRenderingLoop()
{
if(!_isRenderLoopRunning)
{
_renderLoopController = DispatcherQueueController::CreateOnDedicatedThread();
_isRenderLoopRunning = _renderLoopController.DispatcherQueue().TryEnqueue([this]()
{
while(_isRenderLoopRunning)
{
Concurrency::critical_section::scoped_lock lock(_criticalSection);
if(_renderer->Render())
{
Present();
}
// Halt the thread until the next vblank is reached.
// This ensures the app isn't updating and rendering faster than the display can refresh,
// which would unnecessarily spend extra CPU and GPU resources. This helps improve battery life.
_dxgiOutput->WaitForVBlank();
}
});
}
}
The associated member variables in the C++ header file looks as follows:
private:
concurrency::critical_section _criticalSection;
winrt::Microsoft::UI::Dispatching::DispatcherQueueController _renderLoopController{ nullptr };
bool _isRenderLoopRunning = false;
In order to stop the rendering thread the destructor of the 3D component contains the following code:
MyCustomView::~MyCustomView()
{
_isRenderLoopRunning = false;
_renderLoopController.ShutdownQueueAsync();
}
When the 3D component gets destroyed, the Visual C++ runtime throws an assertion that looks as follows:
Debug Assertion Failed!
Program: MyAppPackage\CONCRT140D.dll
File: d:\agent\_work\18\s\src\vctools\crt\crtw32\concrt\rtlocks.cpp
Line: 1001
Expression: Lock was destructed while held
The callstack that I can obtain looks as follows:
concrt140d.dll!Concurrency::critical_section::~critical_section() + 79 bytes Unknown
MyCustomComponents.dll!winrt::MyCustomComponents::implementation::BaseView::~BaseView() C++
MyCustomComponents.dll!winrt::implements<winrt::MyCustomComponents::implementation::MyCustomView,winrt::MyCustomComponents::MyCustomView,winrt::MyCustomComponents::implementation::BaseView,winrt::no_module_lock>::~implements<winrt::MyCustomComponents::implementation::MyCustomView,winrt::MyCustomComponents::MyCustomView,winrt::MyCustomComponents::implementation::BaseView,winrt::no_module_lock>() C++
MyCustomComponents.dll!winrt::MyCustomComponents::implementation::MyCustomView_base<winrt::MyCustomComponents::implementation::MyCustomView,winrt::MyCustomComponents::implementation::BaseView>::~MyCustomView_base<winrt::MyCustomComponents::implementation::MyCustomView,winrt::MyCustomComponents::implementation::BaseView>() C++
MyCustomComponents.dll!winrt::MyCustomComponents::implementation::MyCustomView::~MyCustomView() Line 32 C++
I'm struggling to understand whether the assertion Lock was destructed while held
points to an actual problem or whether it is safe to ignore this. If that is an actual problem, how would I address it properly?
Edit:
The call stack points to BaseView
from which MyCustomView
inherits. The simplified BaseView
looks as follows:
#pragma once
#include <concrt.h>
namespace winrt::MyNamespace
{
struct BaseView : BaseViewT<BaseView>
{
protected:
concurrency::critical_section _criticalSection;
// Several methods
};
}
There is no constructor or destructor implementation of that class. But it constructs the _criticalSection
as shown above.