【问题标题】:How can I create a synchronous wrapper around an async callback in C++?如何在 C++ 中围绕异步回调创建同步包装器?
【发布时间】:2022-01-20 21:12:36
【问题描述】:

我正在使用 C++ 中的 libportal 库来改进对现有应用程序的 Flatpak 支持,我遇到了一个问题,即 libportal 公开的许多 API 都是异步的,但代码库我正在使用的编写方式是假设相关操作是同步的。当然,我可以重构代码以支持异步操作,但这需要对应用程序的代码库进行重大更改,并且对于我需要 libportal 的目的而言,它不会带来明显的好处,主线程被阻塞并不重要.我与一位 libportal 维护者交谈过,他说他对添加我想使用的 API 的同步变体不感兴趣 (xdp_portal_open_file),而是建议我自己制作一个同步包装器,这最终让我我的问题:

如何在 C++ 中为基于回调的异步函数制作同步包装器?在这种情况下,回调是来自Gio 2.0GAsyncReadyCallback

【问题讨论】:

  • 简短的回答是:对于每个特定的用例,您要弄清楚如何去做。世界上每个这样的图书馆都没有通用的、按数字绘制的方法。你坐下来,浏览图书馆的参考资料,直到你完全理解它是如何工作的。一旦完全理解了库是如何开展业务的,就应该清楚如何为其实现替代 API 包装器。
  • @SamVarshavchik 在这种情况下,是否最好改写它,以便它询问如何在 libportal 的上下文中执行此操作?我并不是说我在期待别人为我做这项工作,但我什至不知道如何从这里开始。不,这不是我得到报酬的事情。

标签: c++ asynchronous callback synchronization asynccallback


【解决方案1】:

我使用条件变量为您创建了一个示例。 我没有你的框架,所以我使用 std::async 来模拟异步调用。

这个例子基本上是启动异步调用,让回调在异步函数完成时设置一个信号。然后调用线程将阻塞,直到设置标志(同时不花费任何 CPU 时间)。

#include <chrono>
#include <future>
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>

// use a condition variable, a mutex and a flag to build a synchronization point.
// condition variables are more like a signal between threads then a real variable!
// and are used a lot to do inter-thread signaling, because the avoid busy loops
class synchronization_point_t
{
public:
    void set()
    {
        std::unique_lock<std::mutex> lock{ m_mtx };
        m_flag = true;
        m_cv.notify_all();
    }

    void wait()
    {
        std::unique_lock<std::mutex> lock{ m_mtx };
        m_cv.wait(lock, [&] {return m_flag; });
    }

private:
    std::mutex m_mtx;
    std::condition_variable m_cv;
    bool m_flag{ false };
};

//-----------------------------------------------------------------------------

void callback(void* userdata)
{
    std::cout << "callback setting synchronization point signal\n";
    auto synchronization_point = reinterpret_cast<synchronization_point_t*>(userdata);
    synchronization_point->set();
}

void async_fn(void* userdata)
{
    std::cout << "async_fn starting\n";

    // simulate some work here
    for (std::size_t n = 0; n < 10; ++n)
    {
        std::cout << ".";
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    std::cout << "\n";

    std::cout << "async_fn calling callback\n";
    callback(userdata);
    std::cout << "async_fn done\n";
}


void fn()
{
    std::cout << "fn() creating a synchronization point\n";
    synchronization_point_t synchronization_point;

    std::cout << "fn() calling the asynchronous variant\n";
    auto future = std::async(std::launch::async, [&]
    {
        async_fn(reinterpret_cast<void*>(&synchronization_point));
    });

    // this will wait until the function has called the callbacks
    std::cout << "fn() waiting for the asynchronous call to complete\n";
    synchronization_point.wait();
    std::cout << "fn() done\n";
}

int main()
{
    std::cout << "main calling synchronous version of fn()\n";
    fn();
    std::cout << "main done\n";
    return 0;
}

【讨论】:

  • 我无法建议编辑,因为队列已满,但至少在 GCC 11 中,只有在您也 #include &lt;thread&gt; 时才能编译。除此之外,谢谢!我正在深入研究它以确保我首先理解它,但当我完成后,我可能会接受这个作为我问题的答案。
  • 条件变量在你第一次看到它们的时候是有点意思的,不要犹豫,问问题。我添加了'''#include '''。通常我在 MSVC/C++17(或 C++20 设置)上编译
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-26
  • 2011-10-25
  • 2015-12-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多