У меня есть определенный класс исключений (используется как исключение отмены потока, где я делаю точки отмены, выбрасывающие его).
Я хочу иметь возможность вызывать abort (), если пользователь ловит его, но не выбрасывает. Чтобы пользователь не мог отменить отмену или непреднамеренно использовать catch (...).
Я пытался вызвать abort () в деструкторе, если не установлен частный флаг в классе исключений. Функция запуска моего потока является другом этого класса исключений и изменяет внутренний флаг. Проблема в том, что в Visual C ++ исключение уничтожается дважды (по-видимому, потому что оно копируется при выдаче).
Я решил использовать подсчет ссылок, чтобы при копировании (которое, по-видимому, происходит во время выполнения оператора throw) счетчик увеличивался, и деструктор не выбрасывал прерывание, пока есть другие копии.
К сожалению, конструктор копирования не на самом деле вызывается - я пытался попытаться от него избавиться. То же самое для операторов присваивания и перемещения конструктора и присваивания - они не вызываются. Похоже, что исключение магически продублировано, и ни один из них не вызван, поскольку деструктора дважды бросают.
Так какой обходной путь я могу использовать здесь?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EDIT ~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
Добавлен пример кода для объяснения:
C ++ 0x std :: thread конструктор принимает функтор или функцию и 0..n подвижные аргументы. Поскольку MSVC не поддерживает шаблоны переменных, я использовал перегруженные конструкторы для различного числа аргументов. Например, для двух аргументов:
template<typename C, typename A0, typename A1>
inline thread::thread(C const &func, A0 &&arg0, A1 &&arg1)
{
Start(make_shared<function<void (void)>>(function<void (void)>(std::bind(forward<C>(func), forward<A0>(arg0), forward<A1>(arg1)))));
}
Далее Start () упаковывает функцию std :: function с навязчивым умным указателем, чтобы удостовериться, что функция std :: правильно уничтожена, когда она выходит за рамки как в Start (), так и в потоке Run (). функция запущена _beginthreadex. Внутри функции run, try / catch, который фактически запускает предоставленную пользователем функцию / аргументы, обычно будет:
try
{
(*pack->runpack)();
}
catch (...)
{
terminate();
}
Однако я хотел бы иметь возможность, в дополнение к обычной функциональности std :: thread, делать что-то вроде того, что происходит, когда в Linux вы вызываете pthread_cancel (), и, кроме того, иметь возможность выполнять любой из аналогов типа отмены из PTHREAD_CANCEL_DEFERRED и PTHREAD_CANCEL_ASYNCHRONOUS. Это важно, поскольку я пишу для приложения-киоска, где основное приложение должно иметь возможность восстанавливаться (большую часть времени) из потоков, зависших при запуске сторонних созданных модулей.
Я добавил следующее выше catch (...)
выше:
catch (Cancellation const &c) // TODO: Make const & if removing _e flag from Cancellation; TODO: Catch by const & if not modifying internal state
{
c.Exec();
}
, где Cancellation :: Exec () использует указатель на текущий std :: thread (который я храню в локальном потоке) и вызывает detach (). Затем я добавил две функции, первая из которых:
bool CancelThreadSync(std::thread &t, unsigned int ms)
{
if (!QueueUserAPC(APCProc, t.native_handle(), 0)) THROW_LASTWINERR(runtime_error, "Could not cancel thread")
if (ms) Wait(ms);
if (t.joinable()) return false;
return true;
}
APCProc устанавливает некоторый флаг, чтобы я мог добавить TestCancel (), аналогично функции точки отмены pthread_testcancel (). TestCancel () выдает Cancellation, если установлен флаг (pthreads в Linux приводит к тому, что стек разматывается и деструкторы вызываются по-разному, но это делает это, так что это намного лучше, чем TerminateThread ()). Однако я также изменил все места ожидания, такие как WaitForSingleObject и Sleep, на версии этих функций с предупреждением, а также WaitForSingleObjectEx и SleepEx. Например:
inline void mutex::lock(void)
{
while (Load(_owned) || _interlockedbittestandset(reinterpret_cast<long *>(&_waiters), 0))
{
unsigned long waiters(Load(_waiters) | 1);
if (AtomicCAS(&_waiters, waiters, waiters + 512) == waiters) // Indicate sleeping
{
long const ret(NtWaitForKeyedEvent(_handle, reinterpret_cast<void **>(this), 1, nullptr)); // Sleep
#pragma warning(disable : 4146) // Negating an unsigned
AtomicAdd(&_waiters, -256); // Indicate finished waking
#pragma warning(default : 4146)
if (ret)
{
if (ret != STATUS_USER_APC) throw std::runtime_error("Failed to wait for keyed event");
TestCancel();
}
}
}
}
(оригинальный алгоритм от http://www.locklessinc.com/articles/)
Обратите внимание на проверку того, предупредил ли APC ожидание.Теперь я могу отменить запущенный поток, даже если он заблокирован.TestCancel () создает исключение Cancellation, поэтому стек разматывается деструкторами, вызываемыми до завершения потока - это именно то, что я хочу.Я также добавил асинхронное аннулирование для случаев, когда блок находится не в системном вызове, а, возможно, в бесконечном цикле или в действительно медленном вычислении или в тупике спин-блокировки.Я сделал это, приостановив поток, установив Eip (или Rip на x64), чтобы он указывал на функцию, которая выдает Cancellation, и возобновил ее.Это не совсем надежно, но работает в большинстве случаев, и для меня этого достаточно.
Я проверил что-то, и они отлично работают.Моя проблема заключается в том, чтобы пользователь не смог отменить отмену без ее повторного удаления.Я хочу, чтобы это всегда было разрешено распространяться на функцию запуска потока.Поэтому я подумал, что вызову abort () в ~ Cancellation (), если не установлен внутренний флаг (где функция запуска потока является другом Cancellation и единственной, которая может это сделать).Проблема в том, что происходит множественный вызов деструктора, и моя неспособность обойти это с помощью подсчета ссылок, поскольку дублирование исключения, по-видимому, происходит без вызова какого-либо вызываемого конструктора / назначения копирования / перемещения, насколько я могу судить (Я протестировал, добавив операторы печати ко всем этим).