问题
When compiling managed C++ code with the /clr flag, the compiler does not allow the include. I am trying to port my unmanaged C++ code into a managed C++ environment. I see that C# has the alternatives Task and TaskCompletionSource to replace futures and promises but I do not see these options available in managed C++. I need to perform interop with some C++ unmanaged libraries so I cannot switch to C# completely. I still need a C++ layer in between. How can I achieve future/promise functionality in managed C++?
Here is an example of unmanaged code in C++ which compiles without the /clr flag:
int Foo(std::future<int> &fur) {
int result = 1;
int value = fut.get();
// Do something with value
return result;
}
int main() {
int x;
std::promise<int> p;
std::future<int> f = p.get_future();
std::future<int> fut = std::async(Foo, std::ref(f));
int val = 1;
p.set_value(val);
x = fut.get();
}
I want to be able to do this in C++/CLI
回答1:
Update (some of the early comments were reformulated here):
Many of the standard C++ libraries are free to use even if we choose common language runtime support (C++-Cli, CLR). But some of them are not available, for example the questioned ones.
In case we add such header we get the following error:
' <future>
is not supported when compiling with /clr or /clr:pure. '
This means for us that such code may stay in a separate dll OR we have to refactor our code OR we need a C# implementation of the mentioned libraries like this one.
Answer:
The question itself contains the correct answer, so the future and primes could be implemented using the following:
Task<T>
is a future (or Task for a unit-returning future),TaskCompletionSource<T>
is a promise,
was also shown in the post here. This means we can simply search C# replacement for future and promise and then translate them to C++-Cli.
In my answer I only show some key points how to translate and use Task
and built in delegates. Task itself is easy:
#include "stdafx.h"
using namespace System;
using namespace System::Threading::Tasks;
public ref class MyActions
{
public:
MyActions()
{
// lambdas are not allowed for managed class so we use built in delegates
auto t = gcnew Task(gcnew Action<Object^>(task1),this);
}
public:
static void task1(Object^ o)
{
// TODO:
printf("Hello World! [c++-cli] and [win32]");
}
};
As you can see the C++-Cli syntax is fairly similar to c#, and you still have most of the 'old' C++.
NOTE: We should still keep class definition and implementation separate (myactions.h and myactions.cpp)
回答2:
C++ / CLI doesn't allow including any of the standard headers <mutex>
, <future>
, <thread>
& <condition_variable>
(BTW in VS2017 you could use <atomic>
). It also does not allow the use of the concurrency runtime offered by microsoft.
Since you are using C++ / CLI, I assume portability is not an issue. My suggestion is that you roll your own mutex, condition_variable, future and promise using thin wrappers around the windows API.
A while ago I wrote some code that does just that. it is not as hard as it might sound.
Note:
- this appended code is not meant to be standard conformant in any way.
- srwlock is a replacement for std::mutex and can even be used with std::lock_guard / std::unique_lock.
- srwcondition_variable is a replacement for std::condition_variable.
- unique_srwlock is a replacement for std::unique_lock (for use with srwcondition_variable).
- srfuture & srpromise are replacements for std::future & std::promise.
:
#include <windows.h>
class srwlock
{
SRWLOCK _lk{};
public:
void lock()
{
AcquireSRWLockExclusive(&_lk);
}
void unlock()
{
ReleaseSRWLockExclusive(&_lk);
}
void lock_shared()
{
AcquireSRWLockShared(&_lk);
}
void unlock_shared()
{
ReleaseSRWLockShared(&_lk);
}
SRWLOCK* native_handle()
{
return &_lk;
}
srwlock() = default;
srwlock(const srwlock&) = delete;
srwlock& operator=(const srwlock&) = delete;
srwlock(srwlock&&) = delete;
srwlock& operator=(srwlock&&) = delete;
};
template <typename _Lk>
class unique_srwlock
{
_Lk& _lk;
friend class srwcondition_variable;
public:
void lock()
{
_lk.lock();
}
void unlock()
{
_lk.unlock();
}
unique_srwlock(_Lk& lk) : _lk(lk)
{
_lk.lock();
};
~unique_srwlock()
{
_lk.unlock();
};
unique_srwlock(const unique_srwlock&) = delete;
unique_srwlock& operator=(const unique_srwlock&) = delete;
unique_srwlock(unique_srwlock&&) = delete;
unique_srwlock& operator=(unique_srwlock&&) = delete;
};
enum class srcv_status
{
timeout, no_timeout
};
class srwcondition_variable
{
CONDITION_VARIABLE _cv{};
public:
void wait(srwlock& lk)
{
VERIFY_TRUE(SleepConditionVariableSRW(&_cv, lk.native_handle(), INFINITE, 0));
}
void wait(unique_srwlock<srwlock>& lk)
{
VERIFY_TRUE(SleepConditionVariableSRW(&_cv, lk._lk.native_handle(), INFINITE, 0));
}
srcv_status wait_for(unique_srwlock<srwlock>& lk, const std::chrono::milliseconds& timeout_duration)
{
auto val = SleepConditionVariableSRW(&_cv, lk._lk.native_handle(), static_cast<DWORD>(timeout_duration.count()), 0);
if (val != 0)
{
return srcv_status::no_timeout;
}
else
{
if (GetLastError() == ERROR_TIMEOUT)
{
return srcv_status::timeout;
}
else
{
throw std::runtime_error("wait_for unexpected return value in SleepConditionVariableSRW");
}
}
}
void notify_one()
{
WakeConditionVariable(&_cv);
}
void notify_all()
{
WakeAllConditionVariable(&_cv);
}
srwcondition_variable() = default;
srwcondition_variable(const srwcondition_variable&) = delete;
srwcondition_variable& operator=(const srwcondition_variable&) = delete;
srwcondition_variable(srwcondition_variable&&) = delete;
srwcondition_variable& operator=(srwcondition_variable&&) = delete;
};
class bad_srfuture : public std::runtime_error
{
public:
bad_srfuture(const char* msg) : std::runtime_error(msg) {}
};
inline void throw_bad_srfuture(bool isValid)
{
if (!isValid)
{
throw bad_srfuture("no state");
}
}
#ifdef _DEBUG
#ifndef FUTURE_THROW_ON_FALSE
#define FUTURE_THROW_ON_FALSE(stmt) throw_bad_srfuture(stmt);
#endif
#else
#ifndef FUTURE_THROW_ON_FALSE
#define FUTURE_THROW_ON_FALSE(stmt) __noop
#endif
#endif
enum class srfuture_status
{
deffered, ready, timeout
};
namespace private_details
{
template <typename T>
class future_shared_state
{
public:
void wait() const
{
// wait until there is either state or error
unique_srwlock<srwlock> lk(_cs);
while (!_state && !_error)
{
_available.wait(lk);
}
}
srfuture_status wait_for(const std::chrono::milliseconds& timeout_duration)
{
// wait until there is either state or error
unique_srwlock<srwlock> lk(_cs);
while (!_state && !_error)
{
auto cv_status = _available.wait_for(lk, timeout_duration);
if (cv_status == srcv_status::timeout)
{
return srfuture_status::timeout;
}
}
return srfuture_status::ready;
}
T& get()
{
if (_state) return *_state;
if (_error) std::rethrow_exception(_error);
throw std::logic_error("no state nor error after wait");
}
template <typename U>
void set_value(U&& value)
{
unique_srwlock<srwlock> lk(_cs);
if (_state.has_value() || _error != nullptr)
{
throw bad_srfuture("shared state already set");
}
_state.emplace(std::forward<U>(value));
_available.notify_all();
}
void set_exception(std::exception_ptr e)
{
unique_srwlock<srwlock> lk(_cs);
if (_state.has_value() || _error != nullptr)
{
throw bad_srfuture("shared state already set");
}
_error = e;
_available.notify_all();
}
private:
mutable srwlock _cs; // _state protection
mutable srwcondition_variable _available;
std::optional<T> _state;
std::exception_ptr _error;
};
} // namespace private_details
template <typename T> class srpromise;
template <typename T>
class srfuture {
public:
srfuture() noexcept = default;
~srfuture() = default;
srfuture(srfuture const& other) = delete;
srfuture& operator=(srfuture const& other) = delete;
srfuture& operator=(srfuture&& other) noexcept = default;
srfuture(srfuture&&) noexcept = default;
T get()
{
// get is assumed to be called from a single thread.
// step 1: pass the _shared_state to the current thread waiter (invalidate)
// (other threads calling get() will result in undefined behaviour as _shared_state will be nullptr
auto shared_state = std::move(_shared_state);
FUTURE_THROW_ON_FALSE(shared_state != nullptr);
// step 2: safely wait for the shared state to fulfill
shared_state->wait();
// shared state is fulfilled and no exception is set.
// step 3: move / copy the state:
return std::move(shared_state->get()); // https://stackoverflow.com/questions/14856344/when-should-stdmove-be-used-on-a-function-return-value
}
bool valid() const noexcept
{
return _shared_state != nullptr;
}
void wait() const
{
// The behavior is undefined if valid() == false before the call to this function.
FUTURE_THROW_ON_FALSE(valid());
_shared_state->wait();
}
srfuture_status wait_for(const std::chrono::milliseconds& timeout_duration) const
{
FUTURE_THROW_ON_FALSE(valid());
_shared_state->wait_for(timeout_duration);
}
private:
std::shared_ptr<private_details::future_shared_state<T>> _shared_state = nullptr;
friend class srpromise<T>;
srfuture(const std::shared_ptr<private_details::future_shared_state<T>>& shared_state) : _shared_state(shared_state) {}
srfuture(std::shared_ptr<private_details::future_shared_state<T>>&& shared_state) : _shared_state(std::move(shared_state)) {}
};
template <typename T>
class srpromise
{
public:
srpromise() : _shared_state(std::make_shared<private_details::future_shared_state<T>>()) {}
srpromise(srpromise&& other) noexcept = default;
srpromise(const srpromise& other) = delete;
srpromise& operator=(srpromise&& other) noexcept = default;
srpromise& operator=(srpromise const& rhs) = delete;
~srpromise() = default;
void swap(srpromise& other) noexcept
{
_shared_state.swap(other._shared_state);
}
srfuture<T> get_future()
{
return { _shared_state };
}
void set_value(const T& value)
{
_shared_state->set_value(value);
}
void set_value(T&& value)
{
_shared_state->set_value(std::move(value));
}
void set_exception(std::exception_ptr p)
{
_shared_state->set_exception(std::move(p));
}
private:
std::shared_ptr<private_details::future_shared_state<T>> _shared_state = nullptr;
};
来源:https://stackoverflow.com/questions/45224526/what-is-the-alternative-to-futures-and-promises-in-managed-c