Reputation: 5126
std::thread
class is inherently exception-unsafe since its destructor calls std::terminate
.
std::thread t( function );
// do some work
// (might throw!)
t.join();
You could, of course, put everything in between construction and join()
in a try-catch block, but this can get tedious and error-prone if you know you want to join or detach no matter what happens.
So I was thinking how would one go about writing the simplest possible wrappers around it, but that would also support other hypothetical types of threads. For instance, boost::thread
or something completely different, as long as it had joinable()
, join()
and detach()
methods. Here's how far I've got:
// handles threads safely
// Acts the same as the underlying thread type, except during destruction.
// If joinable, will call join (and block!) during destruction.
// Keep in mind that any exception handling will get delayed because of that;
// it needs to wait for the thread to finish its work first.
template <class UNDERLYING_THREAD = std::thread>
class scoped_thread: public UNDERLYING_THREAD
{
public:
typedef UNDERLYING_THREAD thread_type;
using thread_type::thread_type;
scoped_thread()
: thread_type() {}
scoped_thread( scoped_thread && other )
: thread_type( std::move( other ) ) {}
scoped_thread & operator = ( scoped_thread && other )
{
thread_type & ref = *this;
ref = std::move( other );
return *this;
}
~scoped_thread()
{
if( thread_type::joinable() )
thread_type::join();
}
};
// handles autonomous threads safely
// Acts the same as the underlying thread type, except during destruction.
// If joinable, will call detach during destruction.
// Make sure it doesn't use any scoped resources since the thread can remain
// running after they go out of scope!
template <class UNDERLYING_THREAD = std::thread>
class free_thread
{
// same except it calls detach();
}
This seems to work, but I'm wondering if there is a way to avoid manually defining the constructors and the move assignment operator. Probably the biggest issue I noticed is that compilation will fail if you supply a class with deleted move constructor as a template argument.
Do you have any suggestions about how to possibly avoid this? Or are there other, bigger issues with this approach?
Upvotes: 4
Views: 2310
Reputation: 33019
If you want proper exception handling with asynchronous tasks, maybe you should use std::future
rather than std::thread
. Instead of using join()
, you'd use get()
on the future, and if the future threw an exception, then get()
will result in the same exception.
A simple example:
#include <future>
#include <iostream>
int my_future_task(int my_arg) {
throw std::runtime_error("BAD STUFF!");
return my_arg;
}
int main(int argc, char* argv[]) {
auto my_future = std::async(my_future_task, 42);
try {
my_future.get();
}
catch(std::exception &e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
See also:
Upvotes: 6