C ++ Lambda回调触发事件 [英] c++ lambda callback to trigger event

查看:58
本文介绍了C ++ Lambda回调触发事件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直试图围绕c ++的回调功能。我要实现的目标如下:

I have been trying to wrap my head around the callback functionality in c++. What I am trying to achieve is the following:

我有两个对象,每个对象都有自己的线程。一个对象 A 的指针指向第二个对象 B 。参见示例:

I have two objects, each one with its own thread. One object A has a pointer to the second object B. See example:

class A
{
  public:
   // ...
  private:
   std::unique_ptr<B> b;
};

class B
{
  public:
   void add_message(MessageType msg);
   // ...
};

我要实现的目标是对象 A 使用指向 B 的指针添加一条消息,然后继续执行其他操作,但是具有回调或处理程序,或者在 B 已回复该消息。 B 对消息进行一些处理,并可能将其传递给其他对象以在其自己的线程上进行处理,但最终会给出答复。所以我怎么知道 B 何时回复我的消息,例如:

What I am trying to achieve is having object A add a message using the pointer to B and then continue doing other stuff, but having a callback or handler or something that gets triggered when B has a reply to that message. B does some processing with the message and might pass it to other objects for processing on its own thread but eventually will come up with a reply. So how can I know when B has a reply to my message, for example:

// In class A
MessageType m();
b->add_message(m)
// A's thread continues doing other stuff
...
// some notification that b has a reply?

我知道我可能必须对要使用的回调使用std :: function,但是通过查看许多示例,我无法理解如何做到这一点。感谢您的帮助,请注意,我已经看了很多示例,但不能将其与我试图实现的目标或不理解的目标联系起来。

I know I might have to use std::function for a callback which I would like to use but I can't get my head around how exactly to do this by looking at a lot of examples already. Any help is appreciated and note that I have looked at a lot of examples but can't tie it back to what I am trying to achieve or am not understanding...

推荐答案

线程是执行序列。它们的行为大致类似于线性C ++程序,它们嵌入在内存模型中,可以让它们进行通信并注意到由其他执行线程引起的状态变化。

Threads are sequences of execution. They behave roughly like linear C++ programs, embedded within a memory model that lets them communicate and notice state changes caused by other threads of execution.

对线程的回调不能在没有线程合作的情况下执行序列。您要通知的线程必须明确检查消息是否已到达并进行处理。

A callback to a thread cannot take over a sequence of execution without cooperation from the thread. The thread you want to notify has to explicitly check to see if a message has arrived and process it.

有两个

第一种是类似:future 的方法。在其中,呼叫者获得某种令牌,该令牌代表将来可能会或将要产生的答案。

The first is a std::future like method. In it, the caller gets a token of some kind, and that token represents the answer that may or will be produced in the future.

第二个是只使用消息传递再次。您向B发送一条消息,请求响应。 B将包含响应的消息发送回A。 B收到消息的方式与A收到消息的方式相同。该邮件可能包含某种返回目标,以帮助A将其链接到原始邮件。

The second is to just use messaging again. You send a message to B requesting a response. B sends a message back to A containing the response. The same way that B recieves messages, A recieves messages back. The message may contain a "return target" of some kind to help A link it to the original message.

在基于邮件的系统中,通常有一个事件循环。您可以使用一个线程来重复返回事件循环,而不是使用大型的线性程序。

In a message-based system, it is common to have an "event loop". Instead of a large, linear program, you have a thread that repeatedly returns back to the "event loop". There it checks a queue for messages, and if none are there waits for some.

在这样的系统下,必须将任务分解成一口大小的块,这样您才能检查消息队列。检查事件循环的频率足以响应。

Tasks have to be broken down into bite sized chunks under such a system, so that you check the event loop often enough to be responsive.

一种方法是使用协程,这种执行状态不具有自己的执行程序(例如拥有线程的执行程序)都)。协程会定期放弃优先级并将其状态保存以备后用。

One way to do this is with coroutines, a state of execution without owning its own executor (like a thread, which owns both). Coroutines periodically give up priority and "save their state for later".

将来的解决方案通常是最简单的,但是

The future solution is often the easiest, but it relies on A periodically checking for a response.

首先,是 threaded_queue< T> ,它允许任意数量的生产者和消费者将事情排成一排,然后就把它们吃掉了。

First, a threaded_queue<T>, which lets any number of producers and consumers pass things into a queue and eat them off the front:

template<class T>
struct threaded_queue {
  using lock = std::unique_lock<std::mutex>;
  void push_back( T t ) {
    {
      lock l(m);
      data.push_back(std::move(t));
    }
    cv.notify_one();
  }
  boost::optional<T> pop_front() {
    lock l(m);
    cv.wait(l, [this]{ return abort || !data.empty(); } );
    if (abort) return {};
    auto r = std::move(data.back());
    data.pop_back();
    return std::move(r);
  }
  void terminate() {
    {
      lock l(m);
      abort = true;
      data.clear();
    }
    cv.notify_all();
  }
  ~threaded_queue()
  {
    terminate();
  }
private:
  std::mutex m;
  std::deque<T> data;
  std::condition_variable cv;
  bool abort = false;
};

现在,我们希望将任务传递到这样的队列中,并让一个在get中传递任务结果回来了。这是上面的打包任务的使用:

Now, we want to pass tasks into such a queue, and have the one passing the task in get a result back. Here is a use of the above with packaged tasks:

template<class...Args>
struct threaded_task_queue {
  threaded_task_queue() = default;
  threaded_task_queue( threaded_task_queue&& ) = delete;
  threaded_task_queue& operator=( threaded_task_queue&& ) = delete;
  ~threaded_task_queue() = default;
  template<class F, class R=std::result_of_t<F&(Args...)>>
  std::future<R> queue_task( F task ) {
    std::packaged_task<R(Args...)> p(std::move(task));
    auto r = p.get_future();
    tasks.push_back( std::move(p) );
    return r;
  }
  void terminate() {
    tasks.terminate();
  }
  std::function<void(Args...)> pop_task() {
    auto task = tasks.pop_front();
    if (!task) return {};
    auto task_ptr = std::make_shared<std::packaged_task<R(Args...)>>(std::move(*task));
    return [task_ptr](Args...args){
      (*task_ptr)(std::forward<Args>(args)...);
    };
  }
private:
  threaded_queue<std::packaged_task<void(Args...)>> tasks;
};

如果我没做错,它的工作原理如下:

If I did that right, it works like this:


  • A以lambda的形式向B发送任务。此lambda接受一些固定的参数集(由B提供),并返回一些值。

  • A sends queues a task to B in the form of a lambda. This lambda takes some fixed set of arguments (provided by B), and returns some value.

B弹出队列,并得到 std :: function 接受参数。它调用它;

B pops the queue, and gets a std::function that takes the arguments. It invokes it; it returns void in B's context.

A被赋予了 future< ,它会返回B中的 void 。 R> 在任务排队时。它可以查询它是否完成。

A was given a future<R> when it queued the task. It can query this to see if it is finished.

您会注意到,不能通知 A事情完成了。这需要不同的解决方案。但是,如果A最终达到了无法不等待B的结果就无法进行的程度,那么该系统就会起作用。

You'll note that A cannot be "notified" that things are done. That requires a different solution. But if A eventually gets to a point where it cannot progress without waiting on the result from B, this system works.

另一方面,如果A积累了大量的供给此类消息,有时需要等待许多此类B的输入,直到它们中的任何一个返回数据(或用户执行某些操作)为止,您需要比 std :: future< R> 。通用模式是可靠的,它具有表示将来要交付的计算的令牌。但是,您需要对其进行增强,以使其与将来的计算和消息循环等的多个源一起很好地发挥作用。

On the other hand, if A accumulates a large supply of such messages and sometimes needs to wait on input from many such Bs until any one of them return data (or the user does something), you need something more advanced than a std::future<R>. The general pattern -- having a token that represents future computation to be delivered -- is solid. But you need to augment it to play well with multiple sources of future computation and message loops and the like.

未经测试的代码。

发送消息时 threaded_task_queue 的一种方法是:

One approach for threaded_task_queue when you are sending messages is:

template<class Signature>
struct message_queue;
template<class R, class...Args>
struct message_queue<R(Args...) :
  threaded_task_queue< std::function<R(Args...)> >
{
  std::future<R> queue_message(Args...args) {
    return this->queue_task(
      [tup = std::make_tuple(std::forward<Args>(args)...)]
      ( std::function<R(Args...)> f ) mutable
      {
        return std::apply( f, std::move(tup) );
      }
    );
  }
  bool consume_message( std::function<R(Args...)> f )
  {
    auto task = pop_task();
    if (!task) return false;
    task( std::move(f) );
    return true;
  }
};

在提供者端,您提供 Args ... ,在消费者方面,您消费 Args ... 并返回 R ,在提供者上方面,一旦消费者完成,您就有了 future< R> 才能获得结果。

where on the provider side, you provide Args..., and on the consumer side you consume Args... and return R, and on the provider side you have a future<R> to get the result once the consumer is done.

这可能更多比我写的原始 threaded_task_queue 自然。

This may be more natural than the raw threaded_task_queue I wrote.

std :: apply 是C ++ 17,但是对于C ++ 11和C ++ 14却有很多实现。

std::apply is C++17 but there are implementations in the wild for C++11 and C++14.

这篇关于C ++ Lambda回调触发事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆