C ++: Как вызвать синхронный вызов библиотеки асинхронно? - PullRequest
4 голосов
/ 20 марта 2012

Я работаю с библиотекой, у которой есть блокирующий вызов, который никогда не прерывается, если это не удается. Я хотел бы иметь возможность обрабатывать это состояние ошибки более изящно. Я знаю, что должен быть способ обернуть вызов в рабочем потоке (или некотором другом типе объекта делегата), подождать x количество секунд, а затем выдать исключение, если x количество секунд прошло. Мне нужно сделать это только для одной функции в библиотеке. Как мне реализовать это? Я вижу подобные примеры по всей сети, но ни один из них не делает именно то, что я пытаюсь сделать. Спасибо!

Ответы [ 5 ]

2 голосов
/ 20 марта 2012

Мой ответ «не пытайтесь сделать это».

Конечно, вы, вероятно, можете найти какой-нибудь хак, который будет работать в вашем конкретном случае.Но условия гонки здесь очень трудно исправить.

Очевидный подход состоит в том, чтобы поток A сделал блокирующий вызов, а затем настроил поток B для уничтожения A, если время ожидания истекло.

Но ... Что если время ожидания истечет в то же время, когда A возвращается из блокирующего вызова?В частности, что если B думает, что настало время убить A, то ваш планировщик ОС некоторое время решает запустить A, а затем ваша ОС решает запустить код B, который убиваетA?

Итог: вы убиваете A в какой-то неопределенной точке его исполнения.(Например, возможно, он только что вычел $ 500 со сберегательного счета, но еще не добавил $ 500 к текущему счету. Возможности бесконечны ...)

ОК, так что вы можете создать поток A дляединственная цель запуска библиотечного вызова, а затем сигнализировать условие или что-то еще, когда он заканчивается.По крайней мере, это можно сделать в принципе.Но даже тогда, что, если у самой библиотеки есть какое-то внутреннее состояние, которое остается в несогласованном состоянии, должно A быть убитым в неподходящий момент?

Есть веские причины, по которым асинхронное аннулирование потока было исключено из C ++11 стандарт.Просто сказать нет.Исправьте библиотечную рутину.Сколько бы это ни стоило, в долгосрочной перспективе это почти наверняка дешевле, чем вы пытаетесь.

2 голосов
/ 20 марта 2012

Используя 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).Подробности доступны в документации .

1 голос
/ 20 марта 2012

Очевидно, что поток, в котором сделан блокирующий вызов, не может уничтожить себя - он будет заблокирован.

Один из подходов состоит в том, чтобы запустить поток A, который выполняет блокирующий вызов, затем запустить другой поток B, который неактивен в течение тайм-аута, а затем убивает поток A. Защищенный мьютекс общий флаг может указывать, была ли операция успешной, на основании чего исключение можно бросить или нет.

Второй подход (очень похожий) заключается в запуске потока A, который, в свою очередь, запускает поток B, спит в течение времени ожидания, а затем убивает поток B.

Особенности вашей предпочтительной библиотеки потоков (например, каким потокам разрешено уничтожать друг друга) и характер функции блокировки будут точно влиять на ваши действия.

0 голосов
/ 20 марта 2012

Есть некоторые проблемы.Во-первых, содержит ли библиотека какое-либо внутреннее состояние, которое останется неиспользуемым при неудачном вызове библиотеки?Если это так, то вы застряли, потому что вызовы после сбойного вызова, который заблокирован, также не будут выполнены или, что еще хуже, приведут к ошибочным результатам без каких-либо исключений или других уведомлений.

Если библиотека безопасна, то вы действительно можете попытаться создать потокотключите вызов и подождите некоторое событие с таймаутом.Теперь вам нужно разобраться с проблемами @Nemo - вам нужно позаботиться о том, как вы справляетесь с возвратом результатов.Как именно вы это сделаете, зависит от того, как вы намереваетесь возвращать результаты из потока, который вызывает библиотеку.Как правило, оба потока входят в критическую секцию для безопасного арбитража между потоком lib, возвращающим результаты, и потоком тайм-аута, инструктирующим потоку lib никогда ничего не возвращать (например, путем установки в нем флага), и просто завершаются, если вызов lib когда-либовозвращается.

Сирота в библиотеке.Поток является таким способом приведет к утечке потока, если вызов lib никогда не вернется.Можете ли вы справиться с такими утечками или безопасно прибегнуть к возможному принудительному прекращению осиротевших нитей, зависит от вас и вашего приложения:)

0 голосов
/ 20 марта 2012

В 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 мс (или как хотите). Если это не заканчивается в течение определенного периода времени, вы можете выдать исключение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...