Firstly I'm not familiar with using Async, so the below approach could be completely wrong. I'm currently decoding multiple images and displaying them in a dialog window.
This works fine, as long as I don't attempt to close the dialog while the decodes are still running.
If I do close the dialog, I get a Debug Assertion Error at return hh;
In my class header I have:
class CmytestDlg: public CDialog
{
protected:
// Generated message map functions
BOOL OnInitDialog() override;
std::future<HBITMAP> futurebmps[12];
};
In the .cpp:
BOOL CmytestDlg::OnInitDialog()
{
int totalJobs = 12;
// Queue up all the items,
for (int i = 0; i < totalJobs; i )
{
futurebmps[i] = std::async(std::launch::async,
[this](const int i, const std::string& name) {
// This line is just here to simulate a long running image decode
std::this_thread::sleep_for(std::chrono::seconds(10));
HBITMAP hh = nullptr;
PostMessage(8888, i, 0); // Send a message to display the image
return hh; // <<< Debug Assertion Error Here (If I close dialog to soon)
}, i, std::to_string(0));
}
The message to draw the image is handled by OnRefreshAsyncView
:
LRESULT CmytestDlg::OnRefreshAsyncView(WPARAM wParam, LPARAM lParam)
{
// For the purpose of a minimal demo, even if this is empty It still casuses the issue.
return 0;
}
I believe that the issue is triggered by the call to PostMessage, if I comment out this line, I no longer get the crash.
How do I safely handle the scenario where the dialog is closing? So I can prevent this crash.
CodePudding user response:
My psychic powers suggest that your ATL/MFC program is asserting on the PostMessage call because the window handle became invalid after after you closed/destroyed it. Somewhere in the middle of that PostMessage call is an ASSERT that the m_hWnd
instance is non-null. Hence, the assert is calling attention to a real bug in your code.
PostMessage is is a wrapper for the Win32 API: PostMessage And when you invoke the wrapper function on a different thread, it's likely asserts that m_hWnd
(or similar HWND member) is non-null.
Consider writing additional (thread safe) code (with a mutex) that prevents PostMessage from getting called at the same time or after WM_CLOSE/WM_DESTROY is getting processed on the handle.
Something like this:
void CmytestDlg::SafePostMessage(UINT uMsg, WPARAM wp, LPARAM lp)
{
std::lock<std::mutex> lck(_mutex);
if (_isHwndValid)
{
PostMessage(uMsg, wp, lp);
}
}
void CmytestDlg::OnClose(...)
{
std::lock<std::mutex> lck(_mutex);
_isHwndValid = false;
}
Where OnClose is your handler for a WM_CLOSE message and isHwndValid
is a bool member of your class that gets set to true when the window is created. And _mutex
is a std::mutex.
CodePudding user response:
To extend selbie's answer:
If you want your threads to stop early, you can turn the flag into a std::atomic and check it regularly during your computations, like:
std::mutex _mutex;
std::atomic<bool> _shutdown = false;
void CmytestDlg::LongRunningThing()
{
do_something();
if (_shutdown) return;
do_something_else()
if (_shutdown) return;
...
}
void CmytestDlg::SafePostMessage(UINT uMsg, WPARAM wp, LPARAM lp)
{
std::lock<std::mutex> lck(_mutex);
if (!_shutdown)
{
PostMessage(uMsg, wp, lp);
}
}
void CmytestDlg::OnClose(...)
{
std::lock<std::mutex> lck(_mutex);
_shutdown = false;
}