Home > Net >  App crashes if closing a dialog while Async Operations are still underway
App crashes if closing a dialog while Async Operations are still underway

Time:02-05

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;
}
  • Related