11

这是一个小小的理论问题。想象一个装满传感器的设备。现在,如果传感器 x 检测到某些东西,应该会发生一些事情。同时,如果检测到其他东西,比如两个传感器检测到两个不同的东西,那么这个设备的行为必须不同。

从 webdesign (so javascript) 我了解了事件,例如 (使用 jquery)$(".x").on("click", function(){})或从 angularjs $scope.watch("name_of_var", function())

是否有可能在不使用复杂库的情况下在 C 中复制这种行为?

谢谢。

4

4 回答 4

7

我能想到的一个系统是订阅者通知模型。您可能有一些东西可以处理您的传感器(例如,一个线程对其进行轮询以查看是否发生了某些事情)。当它检测到某些东西时,任务应该提出一种机制以让外部世界知道:这就是通知过程。
另一方面,只有对您的传感器感兴趣的人才会收到通知,因此,这里应该有一个订阅方法来处理这个问题。

现在,困难的部分来了。当传感器处理程序通知世界时,它不能花太多时间这样做,否则它可能会错过其他事件。因此,必须有一个专门用于通知过程的任务(或线程)。另一方面,订阅者希望在收到此类通知事件时更新他们的一些数据。这显然是一个异步过程,因此订阅者必须向通知线程提供回调
最后,您应该使用时间戳标记您的事件,这样接收者将知道他们收到的事件是否已过时以及是否应该丢弃它。
最后的事情可能看起来像下面的代码:


数据结构

/*
 * Some data structures to begin with
 */
struct event;
struct notifier;
struct subscription;
struct notify_sched;


typedef int (*notify_cbck)(struct event *evt, void *private);
/*
 *@type : a value to show the type of event
 *@t : the timestamp of the event
 *@value : a pointer towards the event data
 */
struct event {
    int type;
    struct timeval t; // the timestamp
    void *value;
};

/*
 * @type : the type in which the subscriber is interested
 * @cb : the callback that should be run when an event occur
 * @cb_data : the data to provide to the callback
 * @next,prev : doubly-linked list
 */
struct subscription {
    int type;
    notify_cbck cb;
    void *cb_data;
    struct subscription *next, *prev;
};

/*
 * This structure gathers the subscriptions of a given type.
 * @type : the event type
 * @subs : the subscription list
 * @mutex : a mutex to protect the list while inserting/removing subscriptions
 * @next,prev : link to other typed subscriptions
 */

struct typed_subscription {
    int type;
    struct subscription *subs;
    mutex_t mutex;
    struct typed_subscription *next, *prev;
};

/*
 * @magic : the ID of the event producer
 * @t_subs : the typed_subscription list
 * @mutex : a mutex to protect data when (un)registering new types to the producer
 * @next, prev : doubly-linked list ...
 */
struct notifier {
    int magic;
    struct typed_subscription *t_subs;
    mutex_t mutex;
    struct notifier *next, *prev;
};

/*
 * @ntf : the notifiers list
 * @mutex : a mutex to protect the ntf list
 * @th : something to identify the task that hosts the scheduler
 */
struct notify_sched {
    struct notifier *ntf;
    mutex_t mutex;
    pthread_t th; // I assume it's a classic pthread in this example.
};

我现在没有时间完成我的答案,稍后我将对其进行编辑,为您提供完整的示例。但是从数据结构开始,你应该得到一些想法。无论如何,希望这有点帮助。

于 2013-01-26T14:30:46.953 回答
4

我假设您拥有一个嵌入式系统,可以在单独的线程中访问中断或主要事件循环,否则这是不可能的..

事件处理的基本模型在这里:

#define NOEVENT 0

typedef void *(*EventHandler)(void *);

void *doNothing(void *p){/*do nothing absolutely*/ return NULL; }
typedef struct _event{
  EventHandler handler;
}Event, *PEvent;

Event AllEvents[1000];
unsigned short counter = 0;
void InitEvents()
{
    LOCK(AllEvents);
    for(int i = 0; i < 1000; i++){ 
        AllEvents[i].handler = doNothing;
    }
    UNLOCK(AllEvents);
}
void AddEvent(int EventType, EventHandler ev_handler)
{
    LOCK(AllEvents);
    AllEvents[EventType].handler = ev_handler;
    UNLOCK(AllEvents);
}

void RemoveEvent(int EventType, EventHandler ev_handler)
{
   LOCK(AllEvents);
   AllEvents[EventType] = doNothing;
   UNLOCK(AllEvents); /*to safeguard the event loop*/
}

/*to be run in separate thread*/
void EventLoop()
{
   int event = NOEVENT;
   EventHandler handler;
   while(1){
       while(event == NOEVENT)event=GetEvents();
       handler = AllEvents[event].handler;
       handler();/*perform on an event*/
  }
}

对不起,如果这有点天真..但这是我目前能想到的最好的。

于 2013-01-26T13:43:53.100 回答
0

对于事件,您需要事件循环,它检测实际事件(例如,来自网络的数据)发生,然后生成软件事件结构并调用适当的事件处理程序,或者在更复杂的系统中,事件处理程序链,直到处理程序标记事件被接受。如果没有处理程序,或者没有注册的处理程序接受事件,则忽略事件。

事件循环通常在一个库中,除了库本身可能产生的任何事件之外,它还具有应用程序的 API 以具有事件处理程序,并发送应用程序特定的事件。基于事件的应用程序的一个问题是,使用两个都希望拥有自己的事件循环的库通常很复杂,除非库开发人员特别注意允许使用除库自己之外的其他事件循环。

除非它是一个非常低级别的实时系统,否则事件循环不做忙等待是至关重要的。在 Linux/Unix/Posix 代码中,事件循环通常围绕 select() 或 poll() 系统函数工作。当没有事件时,事件循环调用此函数,超时匹配下一个定时器事件的时间(如果有定时器事件)。除了超时,如果任何指定的文件句柄(通常是网络或 IPC 套接字)准备好进行读/写/错误,以及是否存在未以其他方式处理的中断,select()/poll() 也会返回. 然后事件循环代码检查函数返回的原因,生成和分发必要的事件,当一切都完成后,再次调用 select()/poll() 函数。

在基于事件的系统中同样重要的是,事件处理程序不能阻塞,因为它是由事件循环调用的函数,所以事件循环不会在后台某处继续,处理程序函数调用是事件循环的一部分。因此处理函数必须只处理可用数据,最好是快速处理,然后存储必要的状态以便稍后继续,并返回以等待下一个事件。对于必须阻塞的操作,必须启动另一个线程。同样对于长计算,必须将计算切成小块以允许事件循环也运行,或者必须在另一个线程中进行计算。GUI 应用程序标题栏中烦人的“无响应”通常意味着:应用程序程序员懒惰/无能并阻塞了事件循环,因此它无法对操作系统事件做出反应。

所以,是的,使用 C 构建基于事件的系统非常容易。只需有一个带有 select()/poll() 的循环,定义事件类型,为事件创建数据结构,并有一个函数指针列表对于每个事件类型,使用新的事件结构作为参数调用。

于 2013-01-26T14:05:19.410 回答
0

因为我找到了这条消息,但我并没有完全找到我的解决方案,所以我根据https://prdeving.wordpress.com/2017/04/03/event-driven-programming-with-c-89/在此处发布我的代码为他人。

事件.h

typedef struct s_Arguments {
    char *name;
    int value;
} Arguments; // Treat this as you please
 
typedef struct s_Event {
    char *name;
    Arguments args;
} Event;
 
typedef struct {
    char *name;
    void (*handler)(void*);
} Handler;

struct s_EventContainer {
    int *exitFlag;
    Event *poll;
    Handler *listeners;
    void (*registerEvent)(char *name, void*);
    void (*emit)(char *name, Arguments args);
    int listenersc;
    int pollc;
};

struct s_EventContainer eventContainer;

void _registerEvent(char *name, void*);
void _emitEvent(char *name, Arguments args);

void popPoll();

void find_and_exec_handler_in_listeners(char *name, Arguments *args);

void * fn_eventsDigest(void * p_Data);

void *testFired(Arguments *args);

事件.c

#include "event.h"


int BLOCK_POP, BLOCK_EMIT;


void _registerEvent(char *name, void *cb) {
    LOG("_registerEvent '%s'", name);
    if (!eventContainer.listeners) eventContainer.listeners = malloc(sizeof(Handler));    
    Handler listener = {name, cb};  
    eventContainer.listeners[eventContainer.listenersc] = listener;  
    eventContainer.listenersc++;  
    eventContainer.listeners = realloc(eventContainer.listeners, sizeof(Handler) * (eventContainer.listenersc+1));  
}

void _emitEvent(char *name, Arguments args) {
    LOG("Emit event '%s' with args value %d", name, args.value);
    while(BLOCK_EMIT) asm("nop");
    BLOCK_POP = 1;
    if (!eventContainer.poll) eventContainer.poll = malloc(sizeof(Event));    
    Event poll = {name, args};  
    eventContainer.poll[eventContainer.pollc] = poll;  
    eventContainer.pollc++;  
    eventContainer.poll = realloc(eventContainer.poll, sizeof(Event) * (eventContainer.pollc+1));  
    BLOCK_POP = 0;
}

void popPoll(){
    int* temp = malloc((eventContainer.pollc - 1) * sizeof(Event));
    memcpy(temp, eventContainer.poll+1, (eventContainer.pollc - 1) * sizeof(Event));
    eventContainer.pollc -= 1;
    eventContainer.poll = realloc(eventContainer.poll, eventContainer.pollc * sizeof(Event) + sizeof(Event));
    memcpy(eventContainer.poll, temp, eventContainer.pollc * sizeof(Event));
    temp = NULL;
}



void find_and_exec_handler_in_listeners(char *name, Arguments *args){
    int i;
    for (i=0; i < eventContainer.listenersc; i++) {
        if (strcmp(eventContainer.listeners[i].name, name ) == 0 ) {
            (*eventContainer.listeners[i].handler)(args);
        }
    }
}


void * fn_eventsDigest(void * p_Data) {
    struct s_EventContainer *eventContainer = (struct s_EventContainer *)p_Data;

    BLOCK_POP = 0;
    BLOCK_EMIT = 0;
    
    while(*(eventContainer->exitFlag) == 0) {
        if ( eventContainer->pollc > 0 && BLOCK_POP == 0) {
            BLOCK_EMIT = 1;
            Event ev = eventContainer->poll[0];
            find_and_exec_handler_in_listeners(ev.name, &ev.args);
            popPoll(); 
            BLOCK_EMIT = 0;
        }
    usleep(1*1000);
    }
    printf("Event Loop::exiting...\r\n"); fflush(stdout);
}

void *testFired(Arguments *args) {
    LOG("test event fired with value %d \r\n", args->value);
}

主程序

#include "event.h"
#include <pthread.h>

struct s_EventContainer eventContainer = {
    &Data.exitFlag, //exit loop
    NULL, //poll
    NULL, //listeners
    _registerEvent,
    _emitEvent,
    0,
    0
};

pthread_t events_thread;

int main(int argc, char** argv) {

    eventContainer.registerEvent("test", &testFired);
    
    int ret = pthread_create (&events_thread, NULL, fn_eventsDigest, &eventContainer);
    if (ret) {
        fprintf (stderr, "%s", strerror (ret));
    }
    pthread_setname_np(events_thread,"events_thread");
   
    //....
    sleep(2);
    Arguments args;
    args.name = "test";
    args.value = 10;
    eventContainer.emit("test", args);

    pthread_join (events_thread, NULL);
    return (EXIT_SUCCESS);
}

我愿意接受建议。

于 2020-08-28T16:01:43.830 回答