Skip to main content
Topic: Use main_thread_callback with std::future and std::promise (Read 2072 times) previous topic - next topic
0 Members and 1 Guest are viewing this topic.

Use main_thread_callback with std::future and std::promise

Hi,
I am developing a DSP configuration dialog and I need to call "playback_control::playback_format_title" inside there. Because you can't do this outside the main thread, I have created a main_thread_callback object where I do this call.
Now I have difficulties accessing the result of "playback_format_title" in my configuration dialog again. At the moment, I use std::future and std::promise for this. But this does not work. When click the button that invokes the call and waits for the result, foobar2000 freezes. It doesn't crash, but the windows does not respond and I get a "foobar2000 does not respond anymore." message from Windows.

Here is my code:

Code: [Select]
#include <future>

struct playback_format_title_callback : main_thread_callback
{
public:
  virtual void callback_run() {
    titleformat_object::ptr script;
    static_api_ptr_t<titleformat_compiler>()->compile_safe_ex(script, pattern);
    static_api_ptr_t<playback_control> playback_control;
    pfc::string8 returnVal;
    if (playback_control->playback_format_title(NULL, returnVal, script, NULL, playback_control::display_level_all)) {
      promise.set_value(returnVal);
    } else {
      promise.set_value("");
    }
  }

  void setPattern(const pfc::string8& pat) {
    pattern = pat;
  }

  std::future<pfc::string8> getFuture() {
    return promise.get_future();
  }

private:
  std::promise<pfc::string8> promise;
  pfc::string8 pattern;
};


Code: [Select]
class CMyDSPPopup : public CDialogImpl < CMyDSPPopup > {
  // ...
  void onButtonClick() {
    pfc::string8 pattern = get_pattern();

    service_ptr_t<playback_format_title_callback> cb = new service_impl_t<playback_format_title_callback>();
    cb->setPattern(pattern);
    std::future<pfc::string8> future = cb->getFuture();

    static_api_ptr_t<main_thread_callback_manager> cb_manager;
    cb_manager->add_callback(cb);

    pfc::string8 returnVal = future.get();  // <-- At this point, foobar freezes
    do_something(returnVal);
  }
  // ...
}

static void RunDSPConfigPopup(const dsp_preset & p_data, HWND p_parent, dsp_preset_edit_callback & p_callback) {
  CMyDSPPopup popup(p_data, p_callback);
  if (popup.DoModal(p_parent) != IDOK) p_callback.on_preset_changed(p_data);
}


I already double checked: promise.set_value() is    definitively executed.

Any ideas?

SDK 2015-01-15 / Visual Studio 2013 Update 4 / Whole Program Optimization turned off.

Use main_thread_callback with std::future and std::promise

Reply #1
wild guess, try

Code: [Select]
std::future<pfc::string8>& getFuture() {
    return promise.get_future();
}


Use main_thread_callback with std::future and std::promise

Reply #2
Using references does not change the situation - foobar still freezes.

I also tried another alternative where I initialize the promise in the dialog function:
Code: [Select]
  void onButtonClick() {
    ...
    std::promise<pfc::string8> promise;
    std::future<pfc::string8> future = promise.get_future();
    cb->setPromise(&promise);
    ...
  }

And then in the main_thread_callback:
Code: [Select]
  virtual void callback_run() {
    ...
    promise->set_value(...);
    ...
  }
  void setPromise(std::promise<pfc::string8> *p) {
    promise = p;
  }
private:
  std::promise<pfc::string8> *promise;

Does not work either.

Are there other ways to get a return value out of a main_thread_callback thread?

Use main_thread_callback with std::future and std::promise

Reply #3
Main thread callbacks are queued to run later, when the message pump gets around to it.
If you're in a window procedure on the main thread, you're going to wait an eternity for your callback to run.

If your call is safe to run from a window procedure, just call it immediately (and verify that you're indeed on the main thread).
If your call cannot be safely made from there, you need to queue it up and in the callback spin off whatever followup work you intended to do.

See the helpful comments in the documentation of main_thread_callback:
/*!
Allows you to queue a callback object to be called from main app thread. This is commonly used to trigger main-thread-only API calls from worker threads.\n
This can be also used from main app thread, to avoid race conditions when trying to use APIs that dispatch global callbacks from inside some other global callback, or using message loops / modal dialogs inside global callbacks.
*/

//! Queues a callback object. This can be called from any thread, implementation ensures multithread safety. Implementation will call p_callback->callback_run() once later. To get it called repeatedly, you would need to add your callback again.

Zao shang yong zao nong zao rang zao ren zao.
To, early in the morning, use a chisel to build a bathtub makes impatient people hot-tempered.

Use main_thread_callback with std::future and std::promise

Reply #4
If you're in a window procedure on the main thread, you're going to wait an eternity for your callback to run.


You are right, this might be the issue. I didn't think of this special case.

I think std::future and std::promise are inappropriate in this situation, so I worked out a new solution, based on function objects. And this works fine (as far as I can tell).

Here is my code:

Code: [Select]
struct query_titleformat_task : main_thread_callback
{
public:
  virtual void callback_run() {
    titleformat_object::ptr script;
    static_api_ptr_t<titleformat_compiler>()->compile_safe_ex(script, pattern);
    static_api_ptr_t<playback_control> playback_control;
    pfc::string8 returnVal;
    if (playback_control->playback_format_title(NULL, returnVal, script, NULL, playback_control::display_level_all)) {
      onSuccess(returnVal);
    } else {
      onFailure();
    }
  }
  void setPattern(const pfc::string8& pat) {
    pattern = pat;
  }
  void setOnSuccess(std::function<void(pfc::string8)> f) {
    onSuccess = f;
  }
  void setOnFailure(std::function<void()> f) {
    onFailure = f;
  }
private:
  pfc::string8 pattern;
  std::function<void(pfc::string8)> onSuccess;
  std::function<void()> onFailure;
};


Code: [Select]
class CMyDSPPopup : public CDialogImpl < CMyDSPPopup > {
  static void do_something(pfc::string8 text) {
   // ...
  }
  void onButtonClick() {
    pfc::string8 pattern = get_pattern();
    service_ptr_t<query_titleformat_task> cb = new service_impl_t<query_titleformat_task>();
    cb->setPattern(pattern);
    cb->setOnSuccess(&do_something);
    cb->setOnFailure(std::bind(&do_something, ""));
    static_api_ptr_t<main_thread_callback_manager> cb_manager;
    cb_manager->add_callback(cb);
  }
  //...
}

Use main_thread_callback with std::future and std::promise

Reply #5
Does your do_something method do anything which you cannot do directly from the window procedure? If you have a GUI your window event handlers always run on the main thread. Unless you implement your own message loop which I consider unlikely given the problem statement in the first post.

Use main_thread_callback with std::future and std::promise

Reply #6
If you have a GUI your window event handlers always run on the main thread.

This is not true for DSP configuration dialogs in general. If you call the dialog through foobar "View" > "DSP" menu, then it's a different thread. I already tried it. "playback_control->playback_format_title" will crash foobar if you have called the dialog through "View" > "DSP". (It does not crash however if you have called the DSP dialog through the preferences dialog.)
That's the reason why I need a main_thread_callback in the first place. My do_something procedure is fine. It just assigns the argument to a CEdit field.

Use main_thread_callback with std::future and std::promise

Reply #7
Weird, I wasn't aware of that. This means your do_something method has be be careful how it communicates with the dialog.

I will have to check if I need to update my DSP tutorial.

 
SimplePortal 1.0.0 RC1 © 2008-2019