C ++:如何异步调用同步库调用?

时间:2012-03-20 16:13:04

标签: c++ multithreading asynchronous

我正在使用一个具有阻塞调用的库,如果它不成功,它永远不会超时。我希望能够更优雅地处理这种错误情况。我知道必须有一种方法将调用包装在一个工作线程(或其他类型的委托对象)中,等待x秒,然后如果超过x秒,则抛出异常。我只需要为库中的一个函数执行此操作。我该如何实现呢?我在整个网络上看到了类似的例子,但没有一个正在做我正在尝试做的事情。谢谢!

5 个答案:

答案 0 :(得分:2)

使用C ++ 11然后为该调用显式启动一个线程可能如下所示:

// API
T call(int param);

// asynchronous call with 42 as parameter
auto future = std::async(std::launch::async, call, 42);
// let's wait for 40 ms
auto constexpr duration = std::chrono::milliseconds(40);
if(future.wait_for(duration) == std::future_status::timeout) {
    // We waited for 40ms and had a timeout, now what?
} else {
    // Okay, result is available through future.get()
    // if call(42) threw an exception then future.get() will
    // rethrow that exception so it's worth doing even if T is void
    future.get();
}

正如你所看到的那样,在超时的情况下你会遇到一个很大的问题,因为你永远陷入了被阻止的线程。这可能不是C ++ 11 std::future的错误:相当多的线程抽象将提供最好的合作取消,而这仍然不足以拯救你。

如果您没有使用C ++ 11,那么Boost.Thread与boost::unique_future具有非常相似的界面(其中wait_for代替timed_wait,并返回bool ),虽然它没有类似于std::async的东西,所以你必须自己做一些繁忙的工作(例如boost::packaged_task + boost::thread)。详细信息in the documentation

答案 1 :(得分:2)

我的回答是“不要试图这样做。”

当然,您可能会发现一些看似适合您特定情况的黑客攻击。但这里的竞争条件很难解决。

显而易见的方法是让线程A进行阻塞调用,然后设置线程B以在超时到期时终止A

但是......如果超时在阻塞呼叫返回A的同时到期怎么办?具体来说,如果B 认为是时候杀死A,那么您的操作系统调度程序决定运行A一段时间,然后您的操作系统决定运行杀死B的{​​{1}}代码?

结论:你最终会在执行中的某个不确定点杀死A。 (例如,它可能只是从储蓄账户中扣除了500美元,但尚未向支票账户增加500美元。可能性无穷无尽......)

好的,所以你可以让线程A存在,仅用于运行库调用,然后在完成时发出条件或其他信号。至少原则上可以使这项工作成为可能。但即便如此,如果图书馆本身有一些内部状态处于不一致的状态会导致A在不合时宜的时刻被杀?

有很好的理由从C ++ 11标准中省略了异步线程取消。拒绝吧。修复库例程。无论成本如何,从长远来看,几乎肯定比你尝试的更便宜。

答案 2 :(得分:1)

显然,阻塞调用所在的线程不会自行终止 - 它将被阻止。

一种方法是启动一个进行阻塞调用的线程A,然后启动另一个休眠超时然后杀死线程A的线程B.一个互斥锁保护的共享标志可以指示操作是否成功,基于哪个异常可以抛出或不抛出。

第二种方法(非常相似)是启动一个线程A,然后启动线程B,暂停超时,然后杀死线程B.

您首选的线程库的细节(例如允许相互杀死的线程)以及阻止函数的性质将严重影响您的处理方式。

答案 3 :(得分:0)

在Windows上,您需要执行以下操作:

//your main thread
DWORD threadID;
HANDLE h = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&ThreadProc, 0, 0, &threadID);
DWORD ret = 0xFFFFFF;
for (int i = 0; i < /*some timeout*/; i++) {
    ret = WaitForSingleObject(h, 100);
    if (ret == WAIT_OBJECT_0) break;
}
if (ret != WAIT_OBJECT_0) {
    DWORD exitCode;
    TerminateThread(h, &exitCode); // you will want to stop the thread as it isn't exiting.
    /*throw*/;
}

//Thread Routine
DWORD ThreadProc (LPVOID threadParam) {
    //call your function here
    return 0;
}

这里的想法是启动一个线程来完成你想要的工作。然后,您可以在该线程中等待 100毫秒增量(或任何你想要的)。如果它没有在特定时间段内结束,则可以抛出异常。

答案 4 :(得分:0)

有一些问题。首先,库是否包含任何内部状态,这些内部状态将因失败的库调用而无法使用?如果是这样,那么你就是stuft,因为阻止失败的呼叫后的呼叫也会失败,或者更糟糕的是,在没有任何异常或其他通知的情况下产生错误的结果。

如果库是安全的,那么你确实可以尝试断开调用并等待一些超时的事件。现在您需要处理@Nemo的问题 - 您需要注意如何处理结果的返回。你究竟是如何做到这一点取决于你打算如何从调用库的线程返回结果。通常,两个线程都会进入一个关键部分,以便在lib线程返回结果和指示lib线程永远不会返回任何内容的超时线程之间安全地进行仲裁(例如,通过在其中设置一个标志),并且如果lib调用则只退出回报。

孤立lib。线程是这样一种方式会导致线程泄漏,如果lib调用永远不会返回。无论您是否可以吸收此类泄漏,或者安全地使用最终强制终止孤立线程,都在您和您的应用之间:)