Ответ
Самый удобный способ — использовать std::future, которая автоматически захватывает исключения, брошенные в асинхронной задаче. При вызове get() исключение пробрасывается в вызывающий поток.
Пример с std::async и сбором исключений:
#include <future>
#include <vector>
#include <iostream>
#include <stdexcept>
int risky_operation(int id) {
if (id % 3 == 0) {
throw std::runtime_error("Bad luck from task " + std::to_string(id));
}
return id * 100;
}
int main() {
std::vector<std::future<int>> futures;
// Запускаем несколько асинхронных задач
for (int i = 0; i < 10; ++i) {
futures.emplace_back(std::async(std::launch::async, risky_operation, i));
}
std::vector<std::exception_ptr> exceptions;
std::vector<int> results;
// Собираем результаты и исключения
for (auto& fut : futures) {
try {
results.push_back(fut.get()); // get() пробросит исключение, если оно было
} catch (...) {
exceptions.push_back(std::current_exception()); // Сохраняем исключение для последующей обработки
}
}
// Обрабатываем все собранные исключения
for (const auto& eptr : exceptions) {
try {
if (eptr) std::rethrow_exception(eptr);
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << 'n';
}
}
return 0;
}
Альтернатива для ручного управления потоками: Можно хранить std::exception_ptr в разделяемой переменной, защищенной мьютексом, и каждый поток в блоке catch(...) должен сохранять туда std::current_exception().
Ответ 18+ 🔞
Давай разберём эту тему, а то у меня уже волнение ебать, как люди с исключениями в потоках косячат. Представь: запустил ты кучу задач, они там втихую падают, а ты сидишь и ждёшь результата, как дурак. А потом — бац! — программа накрылась медным тазом, и непонятно, где и что сломалось.
Самый адекватный способ, чтобы не облажаться — это std::future. Она, сука, умная: если в асинхронной задаче вылетело исключение, она его ловит и прячет у себя. А когда ты вызовешь get(), она тебе это исключение аккуратненько в морду выбросит. Красота, да?
Вот смотри, как это выглядит на практике. Берём std::async, запускаем кучу задач, некоторые из них специально падают — чисто для прикола.
#include <future>
#include <vector>
#include <iostream>
#include <stdexcept>
int risky_operation(int id) {
if (id % 3 == 0) {
throw std::runtime_error("Bad luck from task " + std::to_string(id));
}
return id * 100;
}
int main() {
std::vector<std::future<int>> futures;
// Запускаем несколько асинхронных задач
for (int i = 0; i < 10; ++i) {
futures.emplace_back(std::async(std::launch::async, risky_operation, i));
}
std::vector<std::exception_ptr> exceptions;
std::vector<int> results;
// Собираем результаты и исключения
for (auto& fut : futures) {
try {
results.push_back(fut.get()); // get() пробросит исключение, если оно было
} catch (...) {
exceptions.push_back(std::current_exception()); // Сохраняем исключение для последующей обработки
}
}
// Обрабатываем все собранные исключения
for (const auto& eptr : exceptions) {
try {
if (eptr) std::rethrow_exception(eptr);
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << 'n';
}
}
return 0;
}
Суть в чём: мы не пытаемся ловить исключения прямо в рабочем потоке. Мы даём им спокойно вылететь, а future их для нас консервирует. Потом, в основном потоке, когда мы начинаем собирать урожай через get(), мы уже готовы к подвоху. Поймали исключение — сохранили в std::exception_ptr. Это такая умная хуйня, которая может переносить исключение куда угодно. А обработали мы их потом, все разом, чтобы в лог красиво всё вывести.
А если тебе std::async не нравится, и ты сам потоки крутишь, как гонщик? Тогда тебе придётся повозиться. Нужна общая переменная, защищённая мьютексом, куда каждый поток будет складывать своё std::exception_ptr, если его накроет. И в блоке catch(...) каждый твой воркер должен делать std::current_exception() и пихать результат в эту общую копилку. А главный поток потом всё это разгребает. Но это уже, блядь, ручная работа, как будто на дворе 2002-й год. std::async и future — это цивилизованно.