简体   繁体   English

C ++ 11中的异步构造函数

[英]Async constructor in C++11

Sometimes I need to create objects whose constructors take very long time to execute. 有时我需要创建构造函数需要很长时间才能执行的对象。 This leads to responsiveness problems in UI applications. 这导致UI应用程序中的响应性问题。

So I was wondering if it could be sensible to write a constructor designed to be called asynchronously, by passing a callback to it which will alert me when the object is available. 所以我想知道编写一个设计为异步调用的构造函数是否合理,方法是将回调传递给它,这会在对象可用时提醒我。

Below is a sample code: 以下是示例代码:

class C
{
public:
    // Standard ctor
    C()
    {
        init();
    }

    // Designed for async ctor
    C(std::function<void(void)> callback)
    {
        init();
        callback();
    }

private:
    void init() // Should be replaced by delegating costructor (not yet supported by my compiler)
    {
        std::chrono::seconds s(2);
        std::this_thread::sleep_for(s);
        std::cout << "Object created" << std::endl;
    }
};

int main(int argc, char* argv[])
{
    auto msgQueue = std::queue<char>();
    std::mutex m;
    std::condition_variable cv;
    auto notified = false;

    // Some parallel task
    auto f = []()
    {
        return 42;
    };

    // Callback to be called when the ctor ends
    auto callback = [&m,&cv,&notified,&msgQueue]()
    {
        std::cout << "The object you were waiting for is now available" << std::endl;
        // Notify that the ctor has ended
        std::unique_lock<std::mutex> _(m);
        msgQueue.push('x');
        notified = true;
        cv.notify_one();
    };

    // Start first task
    auto ans = std::async(std::launch::async, f);

    // Start second task (ctor)
    std::async(std::launch::async, [&callback](){ auto c = C(callback); });

    std::cout << "The answer is " << ans.get() << std::endl;

    // Mimic typical UI message queue
    auto done = false;
    while(!done)
    {
        std::unique_lock<std::mutex> lock(m);
        while(!notified)
        {
            cv.wait(lock);
        }
        while(!msgQueue.empty())
        {
            auto msg = msgQueue.front();
            msgQueue.pop();

            if(msg == 'x')
            {
                done = true;
            }
        }
    }

    std::cout << "Press a key to exit..." << std::endl;
    getchar();

    return 0;
}

Do you see any drawback in this design? 你觉得这个设计有什么缺点吗? Or do you know if there is a better approach? 或者你知道是否有更好的方法吗?

EDIT 编辑

Following the hints of JoergB's answer, I tried to write a factory which will bear the responsibility to create an object in a sync or async way: 按照JoergB的回答提示,我试着写一个工厂,负责以同步或异步方式创建一个对象:

template <typename T, typename... Args>
class FutureFactory
{
public:
    typedef std::unique_ptr<T> pT;
    typedef std::future<pT> future_pT;
    typedef std::function<void(pT)> callback_pT;

public:
    static pT create_sync(Args... params)
    {
        return pT(new T(params...));
    }

    static future_pT create_async_byFuture(Args... params)
    {
        return std::async(std::launch::async, &FutureFactory<T, Args...>::create_sync, params...);
    }

    static void create_async_byCallback(callback_pT cb, Args... params)
    {
        std::async(std::launch::async, &FutureFactory<T, Args...>::manage_async_byCallback, cb, params...);
    }

private:
    FutureFactory(){}

    static void manage_async_byCallback(callback_pT cb, Args... params)
    {
        auto ptr = FutureFactory<T, Args...>::create_sync(params...);
        cb(std::move(ptr));
    }
};

Your design seems very intrusive. 你的设计似乎非常具有侵入性。 I don't see a reason why the class would have to be aware of the callback. 我没有看到为什么课程必须知道回调的原因。

Something like: 就像是:

future<unique_ptr<C>> constructedObject = async(launchopt, [&callback]() {
      unique_ptr<C> obj(new C());
      callback();
      return C;
})

or simply 或者干脆

future<unique_ptr<C>> constructedObject = async(launchopt, [&cv]() {
      unique_ptr<C> ptr(new C());
      cv.notify_all(); // or _one();
      return ptr;
})

or just (without a future but a callback taking an argument): 或者只是(没有未来但是回调参与争议):

async(launchopt, [&callback]() {
      unique_ptr<C> ptr(new C());
      callback(ptr);
})

should do just as well, shouldn't it? 应该做的一样,不应该吗? These also make sure that the callback is only ever called when a complete object is constructed (when deriving from C). 这些还确保仅在构造完整对象时(从C派生时)调用回调。

It shouldn't be too much effort to make any of these into a generic async_construct template. 将这些中的任何一个变成通用的async_construct模板应该不会太费力。

Encapsulate your problem. 封装你的问题。 Don't think about asynchronous constructors, just asynchronous methods which encapsulate your object creation. 不要考虑异步构造函数,只考虑封装对象创建的异步方法。

It looks like you should be using std::future rather than constructing a message queue. 看起来您应该使用std::future而不是构建消息队列。 std::future is a template class that holds a value and can retrieve the value blocking, timeout or polling: std::future是一个模板类,它包含一个值并可以检索值阻塞,超时或轮询:

std::future<int> fut = ans;
fut.wait();
auto result = fut.get();

I will suggest a hack using thread and signal handler. 我会建议使用线程和信号处理程序进行黑客攻击。

1) Spawn a thread to do the task of the constructor. 1)生成一个线程来完成构造函数的任务。 Lets call it child thread. 让我们称之为子线程。 This thread will intialise the values in your class. 该线程将初始化您的类中的值。

2) After the constructor is completed, child thread uses the kill system call to send a signal to the parent thread. 2)构造函数完成后,子线程使用kill系统调用向父线程发送信号。 (Hint : SIGUSR1). (提示:SIGUSR1)。 The main thread on receiving the ASYNCHRONOUS handler call will know that the required object has been created. 接收ASYNCHRONOUS处理程序调用的主线程将知道已创建所需的对象。

Ofcourse, you can use fields like object-id to differentiate between multiple objects in creation. 当然,您可以使用object-id等字段来区分创建中的多个对象。

My advice... 我的建议...

Think carefully about why you need to do such a long operation in a constructor. 仔细考虑为什么需要在构造函数中执行如此长的操作。

I find often it is better to split the creation of an object into three parts 我发现通常最好将对象的创建分成三个部分

a) allocation b) construction c) initialization a)分配b)构造c)初始化

For small objects it makes sense to do all three in one "new" operation. 对于小型物体,在一个“新”操作中进行所有三个操作是有意义的。 However, heavy weight objects, you really want to separate the stages. 然而,重物,你真的想分开阶段。 Figure out how much resource you need and allocate it. 找出您需要多少资源并分配它。 Construct the object in the memory into a valid, but empty state. 将内存中的对象构造为有效但空的状态。

Then... do your long load operation into the already valid, but empty object. 然后......将长时间加载操作放入已经有效但空的对象中。

I think I got this pattern a long time ago from reading a book (Scott Myers perhaps?) but I highly recommend it, it solves all sorts of problems. 我想我很久以前就读过这本书了(也许是Scott Myers?)但是我强烈推荐它,它解决了各种各样的问题。 For example, if your object is a graphic object, you figure out how much memory it needs. 例如,如果您的对象是图形对象,则可以计算出所需的内存量。 If it fails, show the user an error as soon as possible. 如果失败,请尽快向用户显示错误。 If not mark the object as not read yet. 如果没有将对象标记为尚未读取。 Then you can show it on screen, the user can also manipulate it, etc. Initialize the object with an asynchronous file load, when it completes, set a flag in the object that says "loaded". 然后你可以在屏幕上显示它,用户也可以操作它等。用异步文件加载初始化对象,当它完成时,在对象中设置一个标记为“loaded”的标志。 When your update function sees it is loaded, it can draw the graphic. 当您的更新功能看到它已加载时,它可以绘制图形。

It also REALLY helps with problems like construction order, where object A needs object B. You suddenly find you need to make A before B, oh no!! 它也真的有助于建筑顺序等问题,对象A需要对象B.你突然发现你需要在B之前制作A,哦不! Simple, make an empty B, and pass it as a reference, as long as A is clever enough to know that be is empty, and wait to it is not before it uses it, all is well. 简单,制作一个空的B,并将其作为参考传递,只要A足够聪明地知道它是空的,等到它不是在它使用它之前,一切都很好。

And... Not forgetting.. You can do the opposite on destruction. 并且......不要忘记......你可以在破坏时做相反的事情。 Mark your object as empty first, so nothing new uses it (de-initialisation) Free the resources, (destruction) Then free the memory (deallocation) 首先将你的对象标记为空,所以没有新东西使用它(去初始化)释放资源,(破坏)然后释放内存(释放)

The same benefits apply. 同样的好处也适用。

Having partially initialized objects could lead to bugs or unnecessarily complicated code, since you would have to check whether they're initialized or not. 部分初始化对象可能会导致错误或不必要的复杂代码,因为您必须检查它们是否已初始化。

I'd recommend using separate threads for UI and processing, and then use message queues for communicating between threads. 我建议使用单独的线程进行UI和处理,然后使用消息队列进行线程之间的通信。 Leave the UI thread for just handling the UI, which will then be more responsive all the time. 离开UI线程只是为了处理UI,这将始终更具响应性。

Place a message requesting creation of the object into the queue that the worker thread waits on, and then after the object has been created, the worker can put a message into UI queue indicating that the object is now ready. 将请求创建对象的消息放入工作线程等待的队列中,然后在创建对象之后,工作人员可以将消息放入UI队列,指示对象现在已准备就绪。

Here's yet another pattern for consideration. 这是另一种需要考虑的模式。 It takes advantage of the fact that calling wait() on a future<> does not invalidate it. 它利用了在未来<>上调用wait()不会使其无效的事实。 So, as long you never call get(), you're safe. 所以,只要你从不打电话给get(),你就是安全的。 This pattern's trade-off is that you incur the onerous overhead of calling wait() whenever a member function gets called. 这种模式的权衡是, 无论何时调用成员函数,都会产生调用wait()的繁重开销。

class C
{
    future<void> ready_;

public:
    C()
    {
        ready_ = async([this]
        {
            this_thread::sleep_for(chrono::seconds(3));
            cout << "I'm ready now." << endl;
        });
    }

    // Every member function must start with ready_.wait(), even the destructor.

    ~C(){ ready_.wait(); }

    void foo()
    {
        ready_.wait();

        cout << __FUNCTION__ << endl;
    }
};

int main()
{
    C c;

    c.foo();

    return 0;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM