Skip to content

Instantly share code, notes, and snippets.

@Chronial
Last active October 28, 2021 22:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Chronial/ed1559ec11da2632f6252dce2f1e74da to your computer and use it in GitHub Desktop.
Save Chronial/ed1559ec11da2632f6252dce2f1e74da to your computer and use it in GitHub Desktop.
A portable version of std::async(std::launch::async, ...), see https://github.com/microsoft/STL/issues/949
#pragma once
#include <functional>
#include <future>
#include <chrono>
#include <thread>
#include <type_traits>
template <class V>
class JoiningFuture;
// backport of c++20 jthread, but without stopsource for now
class JThread
{
public:
JThread() = default;
template <class... Args>
[[nodiscard]] explicit JThread(Args&&... args) : mThread(std::forward<Args...>(args...))
{
}
JThread(const JThread&) = delete;
JThread(JThread&&) noexcept = default;
JThread& operator=(const JThread&) = delete;
~JThread() { MaybeJoin(); }
JThread& operator=(JThread&& _Other) noexcept
{
// note: the standard specifically disallows making self-move-assignment a no-op here
MaybeJoin();
mThread = std::move(_Other.mThread);
return *this;
}
[[nodiscard]] bool joinable() const noexcept { return mThread.joinable(); }
void join() { mThread.join(); }
void detach() { mThread.detach(); }
[[nodiscard]] std::thread::id get_id() const noexcept { return mThread.get_id(); }
private:
void MaybeJoin() noexcept
{
if (mThread.joinable()) {
mThread.join();
}
}
std::thread mThread;
};
template <class V>
class SharedJoiningFuture
{
private:
SharedJoiningFuture(std::shared_future<V> future, JThread thread)
: mThread(std::make_shared<JThread>(std::move(thread))),
mFuture(std::move(future)){};
public:
SharedJoiningFuture() = default;
SharedJoiningFuture(const SharedJoiningFuture&) = default;
SharedJoiningFuture(SharedJoiningFuture&& other) = default;
SharedJoiningFuture& operator=(const SharedJoiningFuture&) = default;
SharedJoiningFuture& operator=(SharedJoiningFuture&& other) = default;
decltype(auto) get() const { return mFuture.get(); }
bool valid() const noexcept { return mFuture.valid(); }
bool is_ready() const { return wait_for(std::chrono::seconds(0)) == std::future_status::ready; }
void wait() const { mFuture.wait(); }
template <class Rep, class Period>
std::future_status wait_for(const std::chrono::duration<Rep, Period>& rel_time) const
{
return mFuture.wait_for(rel_time);
}
template <class Clock, class Duration>
std::future_status wait_until(const std::chrono::time_point<Clock, Duration>& abs_time) const
{
return mFuture.wait_until(abs_time);
}
private:
std::shared_ptr<JThread> mThread;
std::shared_future<V> mFuture;
template <class T>
friend class JoiningFuture;
};
template <class V>
class JoiningFuture
{
private:
JoiningFuture(std::future<V> future, JThread thread)
: mThread(std::move(thread)), mFuture(std::move(future))
{
}
public:
JoiningFuture() = default;
JoiningFuture(const JoiningFuture&) = default;
JoiningFuture(JoiningFuture&& other) = default;
JoiningFuture& operator=(const JoiningFuture&) = default;
JoiningFuture& operator=(JoiningFuture&& other) = default;
// calling this method invalidates the future
decltype(auto) get() { return mFuture.get(); }
bool valid() const noexcept { return mFuture.valid(); }
bool is_ready() const { return wait_for(std::chrono::seconds(0)) == std::future_status::ready; }
void wait() const { mFuture.wait(); }
template <class Rep, class Period>
std::future_status wait_for(const std::chrono::duration<Rep, Period>& rel_time) const
{
return mFuture.wait_for(rel_time);
}
template <class Clock, class Duration>
std::future_status wait_until(const std::chrono::time_point<Clock, Duration>& abs_time) const
{
return mFuture.wait_until(abs_time);
}
// calling this method invalidates the future
SharedJoiningFuture<V> share()
{
return SharedJoiningFuture<V>(mFuture.share(), std::move(mThread));
}
private:
JThread mThread;
std::future<V> mFuture;
template <class Func, class... Args>
friend JoiningFuture<std::invoke_result_t<std::decay_t<Func>, std::decay_t<Args>...>> RunInThread(
Func&& func,
Args&&... args);
};
// Standard-conforming implementation of std::async(std::launch::async, ...)
// Hopefully temporary until https://github.com/microsoft/STL/issues/949 is resolved
template <class Func, class... Args>
[[nodiscard]] JoiningFuture<std::invoke_result_t<std::decay_t<Func>, std::decay_t<Args>...>>
RunInThread(Func&& func, Args&&... args)
{
using return_type = std::invoke_result_t<std::decay_t<Func>, std::decay_t<Args>...>;
auto bound_func = std::bind(std::forward<Func>(func), std::forward<Args>(args)...);
std::packaged_task<return_type(void)> task(bound_func);
auto future = task.get_future();
JThread thread(std::move(task));
return JoiningFuture(std::move(future), std::move(thread));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment