1

I'm using std::async in an app where I want to run a function asynchronously, and from that function use SendMessage function to talk to the UI from the worker thread. The following is an extract from an MFC test app that demonstrates what I'm doing:

LRESULT CStdAsyncProjDlg::OnUserPlusOne(WPARAM, LPARAM)
{
    MessageBox("Message", "Hello World Again", MB_OK);

    return 0;
}

// Function that contains the async work.
void TestAsync(HWND hDlg)
{
    // Send a message to the UI from this worker. The WM_USER + 1 message
    // handler is CStdAsyncProjDlg::OnUserPlusOne
    SendMessage(hDlg, WM_USER + 1, 0, 0);
}

// This event is fired when a button is pressed on the MFC dialog.
void CStdAsyncProjDlg::OnBnClickedButton1()
{
    MessageBox("Message", "Hello World", MB_OK);
    std::async(std::launch::async, TestAsync, m_hWnd);
}

In Visual Studio 2013 the above code works as expected. When I press the button I get a message box that says "Hello World", and once I click OK on that message box I get the other message box that says "Hello World Again".

The issue I'm facing is once I migrate the above code to Visual Studio 2015 compiler, the app hangs after the SendMessage function call.

Reading online (answer to this question) it mentions that the destructor for std::future blocks. I've changed the code in Visual Studio 2015 to store the returned std::future from std::async and that seems to fix the issue (the app doesn't hang and I get the second message box). However looking at the std::future code I can't seem to see anything different between Visual Studio 2013 and 2015. So my question is has anything changed in the way std::async works that would cause such behaviour?

Thanks

Community
  • 1
  • 1
Kakalokia
  • 3,191
  • 3
  • 24
  • 42
  • 1
    I'm pretty sure it was a bug in 2013 as the destructor of the future returned by `async` has always been required to block. [This question](http://stackoverflow.com/questions/43600159/msvc-2013-stdasync-works-asynchronously-without-wait) exhibits the same behavior. VS2013 had, well, bad C++11 support. VS2015 update 3 or 2017 is much more compliant. – NathanOliver May 03 '17 at 11:48
  • Ah the wonderful world of `async`, blocking futures and magic deadlocks... Try [this presentation](https://www.youtube.com/watch?v=QIHy8pXbneI), around 42:30. – Kerrek SB May 03 '17 at 11:49
  • 1
    Don't try to SendMessage() from separate thread, it will most probably block. Use PostMessage() – Alexander Ekzhanov May 03 '17 at 12:23
  • Microsoft disagreed with the rest of the c++ universe when it came to handling destructors of unfulfilled futures. As a result, this area of c++ isn't/wasn't portable. It's part of their conspiracy to inhibit technological progress. – Richard Hodges May 03 '17 at 12:43
  • Thanks guys. Much appreciated. – Kakalokia May 03 '17 at 12:45
  • Messages in the range `WM_USER + x` are reserved for use by the window class implementer (presumably the system-provided dialog class in your case). If you need an application private message, use the `WM_APP + x` range of messages. @AlexanderEkzhanov: Calling `SendMessage` across threads is perfectly fine, and a common, lightweight synchronization implementation. `PostMessage`, on the other hand, usually requires **additional** synchronization. – IInspectable May 03 '17 at 12:52
  • @IInspectable This is just a test app to demonstrate the issue. WM_USER was first thing that came to my head. I agree SendMessage is perfectly fine to use across threads. – Kakalokia May 03 '17 at 13:30
  • Sending `WM_USER + 1` is a bug. One you should fix so that it doesn't somehow factor into the problem you are trying to solve. – IInspectable May 03 '17 at 13:35

2 Answers2

4

MSVC had a bug in 2013. The std::async's returned std::future's destructor must block; they deliberately did not have it block because they where arguing for a different standard in committee.

They fixed this before MSVC 2015.

A way to get back most of the old behavior is to write a thread pool class, and submit jobs to it; have it spawn threads whenever all of its threads are working and a new job is submitted. At destruction, the thread pool class cleans up the threads it owns. Then use it's job submission mechanism instead of std::async.

In any case, avoid using std::async on MSVC, as it contains still another standards-defying (if not breaking) feature of using a thread pool limited by some hardware-dependent constant; if you have too many active std::async's, new ones won't start. This is in violation of the advice and spirit given by the C++ standard, but not technically in violation (as the C++ standard gives ridiculous leeway to not-threading thread implementations). It can easily lead to hard to track down bugs that only happen when your application scales up and uses std::async in more spots.

Maybe this was fixed in some iteration of MSVC, but last report I read was that it was still in 2015.

This is another reason to write your own thread pool job queue. At least you'll have control over semantics, instead of getting whatever random broken or half-broken implementation MSVC is currently shipping.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Thanks for that. Fortunately I don't have too many active std::async's, and I'll try to avoid them in the future since the MSVC implementation is not great. I have one question please. If I have my own function that calls std::async internally and returns the result of that (a future), is that safe to do given the returned future will be stored in a variable from the call site of that function? Or do I have to set the future variable inside the function itself? Thanks – Kakalokia May 03 '17 at 13:39
  • @AliAlamiri Yes, future state moves around. Think of them as a wrapper around `unique_ptr`. – Yakk - Adam Nevraumont May 03 '17 at 14:33
1

SendMessage is a blocking call. You should have used PostMessage (fire & forget) for messages to a different thread.

Chris Ryan
  • 11
  • 1