C++实现定时回调函数

23

我想在C++中实现一个系统,以便我可以调用一个函数并要求在X毫秒后调用另一个函数。类似这样:

callfunctiontimed(25, funcName);

25代表在调用函数之前应等待的毫秒数。

我想知道是否需要使用多线程,并使用一些延迟函数?除了使用函数指针外,这样的功能是如何工作的?


1
你应该研究一下std::function。它提供了比函数指针更清晰的接口,还具有绑定变量等附加功能。 - user406009
这不是一个容易的问题或容易解决的问题。基本上,你必须围绕异步事件的多路处理来设计整个应用程序才能正确解决此问题。话虽如此,一旦你理解了它,通常会变得非常容易。 - Kerrek SB
你的目标操作系统是什么?(目标操作系统是哪个?) - Tilman Vogel
4个回答

18
为了实现便携式解决方案,您可以使用boost::asio。以下是我一段时间前编写的演示代码。您可以更改


t.expires_from_now(boost::posix_time::seconds(1));

为了满足您的需求,需要在200毫秒后进行函数调用。

t.expires_from_now(boost::posix_time::milliseconds(200)); 

以下是一个完整的工作示例。它正在重复调用,但我认为只需稍微更改就可以轻松地仅调用一次。

#include <iostream>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

using namespace boost::asio;
using namespace std;

class Deadline 
{
public:
    Deadline(deadline_timer &timer) : t(timer) {
        wait();
    }

    void timeout(const boost::system::error_code &e) {
        if (e)
            return;
        cout << "tick" << endl;
        wait();
    }

    void cancel() {
        t.cancel();
    }


private:
    void wait() {
        t.expires_from_now(boost::posix_time::seconds(1)); //repeat rate here
        t.async_wait(boost::bind(&Deadline::timeout, this, boost::asio::placeholders::error));
    }

    deadline_timer &t;
};


class CancelDeadline {
public:
    CancelDeadline(Deadline &d) :dl(d) { }
    void operator()() {
        string cancel;
        cin >> cancel;
        dl.cancel();
        return;
    }
private:
    Deadline &dl;
};



int main()
{
    io_service io;
    deadline_timer t(io);
    Deadline d(t);
    CancelDeadline cd(d);
    boost::thread thr1(cd);
    io.run();
    return 0;
}



//result:
//it keeps printing tick every second until you enter cancel and enter in the console
tick
tick
tick

我不会因此而给你的回答点踩,但是在两个不同的命名空间中使用“using”会使你的代码自动变得更难以理解。考虑去掉“using”,将命名空间放在行内。这样可以更容易地阅读你所引用的内容,并且也是你在生产代码中应该做的(好的生产代码,而不是草率的周末战士编写的生产代码)。 - searchengine27

7

您希望它异步执行,以便在不阻塞主执行线程的情况下在25毫秒后执行回调函数吗?如果是这样,您可以在您实现的计时器/定时回调函数中从一个单独的线程执行回调函数。

如果您不使用多线程,则在运行睡眠/ usleep 时,您的主要或调用callfunctiontimed(25,funcName)的函数将被阻塞。现在由您决定要实现什么行为。

真正的解决方案不会像多线程或不多线程那么简单。例如,如何考虑到该函数可以多次调用具有不同超时和函数的不同计时器/回调信息。

一种方法是这样的:

  1. Create a sorted list of timers/callbacks, sorted based on the time to expire.
  2. Have on main thread and one thread which looks at callbacks/timers, call it timer thread.
  3. When a new callback is added, add it in the sorted list.
  4. The timer thread could be initialized to wait for the least amount of time in the sorted list, or the head. Re-initialize it as new callbacks get added. There would be some math and conditions to take care of.
  5. As the timer thread is done sleeping, it removes and looks at head of the list and executes the function pointer in a new thread. Timer thread is re-initialized with sleep time on the new head of the list.

    main() {
            //spawn a timer thread with pthread create 
    
        callfunctiontimed(25, test); 
        callfunctiontimed(35, show);
        callfunctiontimed(4,  print);
    }
    
    callfunctionTImed(int time, (func*)function, void*data) //
    {
        //add information to sorted list of timer and callbacks
        //re-initialize sleep_time for timer thread if needed. 
        return. 
    }
    timerThread() {
        while(1){
         sleep(sleep_time);
         //look at head of timer list, remove it, call function in a new thread   
         //adjust sleep time as per new head     
        }
    }
    
希望这能让您了解我的意思,尽管不是完美的,存在一些问题。

这正是我想要的,程序的其余部分继续执行,然后在经过指定的时间后调用指定的函数。 - DavidColson
但在多线程的意义上,是以“中断”的方式还是以事件循环的方式?超时应该拦截当前的主线程执行,还是等待达到适当的状态再执行? - Tilman Vogel
我不希望主程序被中断。我对多线程并不是很了解,只知道它可能能够实现我想要的功能。 - DavidColson

7
许多人在这个问题上都提供了好的答案,但我将直接回答这个问题,因为几年前我遇到了类似的问题。由于某些原因,我不能使用Boost - 我知道Boost在很多开源软件中有很好的应用。此外,我真的想要特别理解定时器和回调函数在基于Linux的环境中的作用。所以,我写了自己的代码。
基本上,我有一个Timer类和一个TimerCallback类。一个典型的回调函数,实现为继承自TimerCallback类的类,将把要在回调函数中执行的操作放置在triggered()方法中,该方法是根据需要专门实现的。
根据通常的语义,一个“Timer”对象与一个回调对象相关联,该回调对象可能包含执行回调所需的所有必要信息。计时器调度由一个全局环境定时器小根堆管理,该小根堆必须在单独的线程/进程中维护。这个小根堆任务只做一件事:将未来设置的回调事件的小根堆化。小根堆可以在O(1)中选择下一个要触发的事件,并且对于n个计时器事件,可以在O(log n)中将其余事件进行小根堆化。它还可以在O(log n)中插入新的计时器事件(在此处阅读有关堆的简介here)。
当定时器触发时,最小堆调度程序会检查它是定期定时器、一次性定时器还是将执行特定次数的定时器。相应地,定时器对象要么从最小堆中删除,要么重新插入到最小堆中以进行下一次执行。如果要删除定时器对象,则从最小堆中删除它(但定时器对象的删除可能由创建它的任务完成或未完成),并对其余堆进行最小堆化;即重新排列以满足最小堆属性。
一个经过工作和单元测试的实现在这里,可能存在漏洞(摘自我的应用程序),但我认为它可能会帮助某些人。该实现基于多进程(fork()),并且在主任务(进程)中使用pthread,并使用POSIX共享内存和POSIX消息队列在进程之间进行通信。

chandank:代码链接在上面的答案中。请尝试访问https://github.com/SonnyRajagopalan/TimerAndCallback。 - Sonny

4
在Windows操作系统中,有一个名为SetTimer的函数 - http://msdn.microsoft.com/en-us/library/windows/desktop/ms644906(v=vs.85).aspx 示例代码:
#define STRICT 1 
#include <windows.h>
#include <iostream.h>

VOID CALLBACK TimerProc(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime) 
{




  cout << "CALLBACK " << dwTime << '\n';
  cout.flush();
}

int main(int argc, char *argv[], char *envp[]) 
{
    int Counter=0;
    MSG Msg;

    UINT TimerId = SetTimer(NULL, 0, 2000, &TimerProc); //2000 milliseconds

    cout << "TimerId: " << TimerId << '\n';
   if (!TimerId)
    return 16;

   while (GetMessage(&Msg, NULL, 0, 0)) 
   {
        ++Counter;
        if (Msg.message == WM_TIMER)
        cout << "Counter: " << Counter << "; timer message\n";
        else
        cout << "Counter: " << Counter << "; message: " << Msg.message << '\n';
        DispatchMessage(&Msg);
    }

   KillTimer(NULL, TimerId);
return 0;
}

需要进行一些修改:1.使用命名空间std;2.将Cast &TimerProc更改为(TIMERPROC);3.设置不使用预编译头文件。除此之外,它运行良好。 - UserX

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接