C:从后台线程返回异步错误的模式?

时间:2009-03-13 06:50:15

标签: c multithreading error-handling

我正在编写一个开源C库。这个库非常复杂,有些操作可能需要很长时间。因此,我创建了一个后台线程来管理长时间运行的任务。

我的问题是我还没有找到一种优雅的方法来从后台线程返回错误。假设后台线程重新组织文件或进行定期维护,它失败了 - 该怎么办?

我目前看到两个选项:

1)如果用户有兴趣看到这些错误,他可以注册一个回调函数。

我不喜欢这个选项 - 用户甚至不知道有后台线程,所以他很可能会忘记设置回调函数。从可用性的角度来看,这个选项很糟糕。

2)后台线程将错误存储在全局变量中,下一个API函数返回此错误。

这就是我目前正在做的事情,但我也不是百分之百满意,因为这意味着用户必须期望从每个API函数返回每个可能的错误代码。即如果后台线程设置IO错误,并且用户只想知道库版本,他将得到IO错误,尽管get_version()API调用根本不访问磁盘。再一次,糟糕的可用性......

还有其他建议/想法吗?

7 个答案:

答案 0 :(得分:3)

也许对于“长时间运行”(你想使用线程的那些)给用户两个选择:

  • 阻止DoAction(...)返回状态
  • 非阻塞DoActionAsync(..., <callback>),将状态提供给用户提供的回调函数

这使用户可以选择如何处理长操作(而不是决定它们),并且很清楚如何返回状态。

注意:我想如果他们调用DoActionAsync,并且用户没有指定回调(例如他们通过null)那么调用不会阻止,但用户不会有/需要处理状态。

答案 1 :(得分:1)

我很想知道完成状态如何通知API的调用者。

由于后台线程执行所有执行。前台线程选择等到完成,就像同步一样。或者前台线程可以执行其他任务,注册回调。

现在,因为第一种方法是同步的,就像你使用全局变量一样。您可以使用包含1个成员的消息队列,而不是全局变量。现在, - 调用者可以轮询消息队列以获取状态 - 调用者可以阻止在消息队列中等待状态

我能想到的,

但如果我是来电者,我想知道进度状态,如果花费的时间非常长。因此,最好是给某种百分比完成或某种东西,以使最终用户能够使用进度条和所有这些来开发更好的应用程序。

答案 2 :(得分:1)

您应该保留错误事件和警告的线程安全列表(或队列)。工作线程可以将事件发布到列表,然后主线程可以从列表中读取事件,一次一个,或批量读取事件以防止竞争条件。理想情况下,主线程应该获取事件队列的副本并刷新它,以便在多个主线程或工作线程的情况下不会发生重复事件的更改。列表中的事件将具有类型和详细信息。

答案 3 :(得分:0)

如果您正在提供一个库并试图通过一个线程隐藏昂贵的工作,我建议不要这样做。如果某些东西很昂贵,那么呼叫者应该可以看到,如果它让他烦恼,他应该自己处理背景/线程。这样他也可以完全控制错误。 它还可以控制他的进程远离使用您的库的开发人员。

如果您仍然想使用线程,我建议您真正遵循回调路由,但要在API和文档中清楚地看到将在此任务上运行后台线程,因此回调是必要的。< / p>

如果您向图书馆的用户提供同步和异步两种方式,那么他们可以选择适合他们的特定情况。

答案 4 :(得分:0)

感谢所有好的答案。你为我提供了许多值得思考的材料。

有些人建议回调。最初,我认为回调是一个好主意。但它只是将问题转移给用户。如果用户收到异步错误通知,他将如何处理?他将不得不打断和/或通知他的同步程序流程,这通常很棘手并经常破坏设计。

我现在正在做的解决方案:如果后台线程生成错误,下一个API调用将返回错误BACKGROUND_ERROR_PENDING。使用单独的API函数(get_background_error()),用户可以查看此错误代码,如果他对此感兴趣的话。

此外,我添加了文档,因此如果返回此错误,用户不会感到太惊讶。

答案 5 :(得分:0)

您可以查看java的Future API,了解处理异步调用和错误的替代机制。如果您愿意,可以使用一些isError()或getError()方法轻松替换已检查的异常。

答案 6 :(得分:0)

我同意肖恩的观点。带有事件循环的消息队列。

如果有错误,后台线程可以插入队列。事件循环将阻塞,直到有新消息可用。

我使用Apache Portable运行时非常成功,这种设计可以构建具有高事务率的电信服务器。它从未失败过。

我使用1个线程插入队列,这将是你的后台线程。事件循环将在另一个线程中运行并阻塞,直到插入新消息。

我建议APR线程池使用APR FIFO队列(也是线程安全的)。

快速设计:

void background_job()
{
    /* There has been an error insert into the queue */
    apr_status_t rv = 0;

    rv = apr_queue_push(queue, data);
    if(rv == APR_EOF) {
        MODULE_LOG(APK_PRIO_WARNING, "Message queue has been terminated");
        return FALSE;
    }
    else if(rv == APR_EINTR) {
        MODULE_LOG(APK_PRIO_WARNING, "Message queue was interrupted");
        return FALSE;
    }
    else if(rv != APR_SUCCESS) {
        char err_buf[BUFFER_SIZE];
        MODULE_LOG(APK_PRIO_CRITICAL, "Failed to push to queue %s", apr_strerror(rv, err_buf, BUFFER_SIZE));

        return FALSE;
    }

    return TRUE;   
}

void evt_loop()
{
   while(continue_loop) {
    apr_status_t rv = 0;

    rv = apr_queue_pop(queue, data);
    if(rv == APR_EOF) {
        MODULE_LOG(APK_PRIO_WARNING, "Message queue has been terminated");
        return FALSE;
    }
    else if(rv == APR_EINTR) {
        MODULE_LOG(APK_PRIO_WARNING, "Message queue was interrupted");
        return FALSE;
    }
    else if(rv != APR_SUCCESS) {
        char err_buf[BUFFER_SIZE];
        MODULE_LOG(APK_PRIO_CRITICAL, "Failed to pop from the queue %s", apr_strerror(rv, err_buf, BUFFER_SIZE));

        return FALSE;
    }

    return TRUE;
   } 
}

如果你想发布更完整的代码,上面只是一些简单的代码片段。

希望有所帮助