上一篇介紹的std::promise通過(guò)set_value可以使得與之關(guān)聯(lián)的std::future獲取數(shù)據(jù)。本篇介紹的std::packaged_task則更為強(qiáng)大,它允許傳入一個(gè)函數(shù),并將函數(shù)計(jì)算的結(jié)果傳遞給std::future,包括函數(shù)運(yùn)行時(shí)產(chǎn)生的異常。下面我們就來(lái)詳細(xì)介紹一下它。
2. std::package_task
在開(kāi)始std::packaged_task之前我們先看一段代碼,對(duì)std::packaged_task有個(gè)直觀的印象,然后我們?cè)龠M(jìn)一步介紹。
#include <thread> // std::thread
#include <future> // std::packaged_task, std::future
#include <iostream> // std::cout
int sum(int a, int b) {
return a + b;
}
int main() {
std::packaged_task<int(int,int)> task(sum);
std::future<int> future = task.get_future();
// std::promise一樣,std::packaged_task支持move,但不支持拷貝
// std::thread的第一個(gè)參數(shù)不止是函數(shù),還可以是一個(gè)可調(diào)用對(duì)象,即支持operator()(Args...)操作
std::thread t(std::move(task), 1, 2);
// 等待異步計(jì)算結(jié)果
std::cout << "1 + 2 => " << future.get() << std::endl;
t.join();
return 0;
}
/// 輸出: 1 + 2 => 3
std::packaged_task位于頭文件#include <future>中,是一個(gè)模板類
template <class R, class... ArgTypes>
class packaged_task<R(ArgTypes...)>
其中R是一個(gè)函數(shù)或可調(diào)用對(duì)象,ArgTypes是R的形參。與std::promise一樣,std::packaged_task支持move,但不支持拷貝(copy)。std::packaged_task封裝的函數(shù)的計(jì)算結(jié)果會(huì)通過(guò)與之聯(lián)系的std::future::get獲取(當(dāng)然,可以在其它線程中異步獲取)。關(guān)聯(lián)的std::future可以通過(guò)std::packaged_task::get_future獲取到,get_future僅能調(diào)用一次,多次調(diào)用會(huì)觸發(fā)std::future_error異常。
std::package_task除了可以通過(guò)可調(diào)用對(duì)象構(gòu)造外,還支持缺省構(gòu)造(無(wú)參構(gòu)造)。但此時(shí)構(gòu)造的對(duì)象不能直接使用,需通過(guò)右值賦值操作設(shè)置了可調(diào)用對(duì)象或函數(shù)后才可使用。判斷一個(gè)std::packaged_task是否可使用,可通過(guò)其成員函數(shù)valid來(lái)判斷。
2.1 std::packaged_task::valid
該函數(shù)用于判斷std::packaged_task對(duì)象是否是有效狀態(tài)。當(dāng)通過(guò)缺省構(gòu)造初始化時(shí),由于其未設(shè)置任何可調(diào)用對(duì)象或函數(shù),valid會(huì)返回false。只有當(dāng)std::packaged_task設(shè)置了有效的函數(shù)或可調(diào)用對(duì)象,valid才返回true。
#include <future> // std::packaged_task, std::future
#include <iostream> // std::cout
int main() {
std::packaged_task<void()> task; // 缺省構(gòu)造、默認(rèn)構(gòu)造
std::cout << std::boolalpha << task.valid() << std::endl; // false
std::packaged_task<void()> task2(std::move(task)); // 右值構(gòu)造
std::cout << std::boolalpha << task.valid() << std::endl; // false
task = std::packaged_task<void()>([](){}); // 右值賦值, 可調(diào)用對(duì)象
std::cout << std::boolalpha << task.valid() << std::endl; // true
return 0;
}
上面的示例演示了幾種valid為false的情況,程序輸出如下
false
false
true
2.2 std::packaged_task::operator()(ArgTypes...)
該函數(shù)會(huì)調(diào)用std::packaged_task對(duì)象所封裝可調(diào)用對(duì)象R,但其函數(shù)原型與R稍有不同:
void operator()(ArgTypes... );
operator()的返回值是void,即無(wú)返回值。因?yàn)閟td::packaged_task的設(shè)計(jì)主要是用來(lái)進(jìn)行異步調(diào)用,因此R(ArgTypes...)的計(jì)算結(jié)果是通過(guò)std::future::get來(lái)獲取的。該函數(shù)會(huì)忠實(shí)地將R的計(jì)算結(jié)果反饋給std::future,即使R拋出異常(此時(shí)std::future::get也會(huì)拋出同樣的異常)。
#include <future> // std::packaged_task, std::future
#include <iostream> // std::cout
int main() {
std::packaged_task<void()> convert([](){
throw std::logic_error("will catch in future");
});
std::future<void> future = convert.get_future();
convert(); // 異常不會(huì)在此處拋出
try {
future.get();
} catch(std::logic_error &e) {
std::cerr << typeid(e).name() << ": " << e.what() << std::endl;
}
return 0;
}
/// Clang下輸出: St11logic_error: will catch in future
為了幫忙大家更好的了解該函數(shù),下面將Clang下精簡(jiǎn)過(guò)的operator()(Args...)的實(shí)現(xiàn)貼出,以便于更好理解該函數(shù)的邊界,明確什么可以做,什么不可以做。
template<class _Rp, class ..._ArgTypes>
class packaged_task<_Rp(_ArgTypes...)> {
__packaged_task_function<_Rp_(_ArgTypes...)> __f_;
promise<_Rp> __p_; // 內(nèi)部采用了promise實(shí)現(xiàn)
public:
// 構(gòu)造、析構(gòu)以及其它函數(shù)...
void packaged_task<_Rp(_ArgTypes...)>::operator()(_ArgTypes... __args) {
if (__p_.__state_ == nullptr)
__throw_future_error(future_errc::no_state);
if (__p_.__state_->__has_value()) // __f_不可重復(fù)調(diào)用
__throw_future_error(future_errc::promise_already_satisfied);
try {
__p_.set_value(__f_(std::forward<_ArgTypes>(__args)...));
} catch (...) {
__p_.set_exception(current_exception());
}
}
};
2.3 讓std::packaged_task在線程退出時(shí)再將結(jié)果反饋給std::future
std::packaged_task::make_ready_at_thread_exit函數(shù)接收的參數(shù)與operator()(_ArgTypes...)一樣,行為也一樣。只有一點(diǎn)差別,那就是不會(huì)將計(jì)算結(jié)果立刻反饋給std::future,而是在其執(zhí)行時(shí)所在的線程結(jié)束后,std::future::get才會(huì)取得結(jié)果。
2.4 std::packaged_task::reset
與std::promise不一樣, std::promise僅可以執(zhí)行一次set_value或set_exception函數(shù),但std::packagged_task可以執(zhí)行多次,其奧秘就是reset函數(shù)
template<class _Rp, class ..._ArgTypes>
void packaged_task<_Rp(_ArgTypes...)>::reset()
{
if (!valid())
__throw_future_error(future_errc::no_state);
__p_ = promise<result_type>();
}
通過(guò)重新構(gòu)造一個(gè)promise來(lái)達(dá)到多次調(diào)用的目的。顯然調(diào)用reset后,需要重新get_future,以便獲取下次operator()執(zhí)行的結(jié)果。由于是重新構(gòu)造了promise,因此reset操作并不會(huì)影響之前調(diào)用的make_ready_at_thread_exit結(jié)果,也即之前的定制的行為在線程退出時(shí)仍會(huì)發(fā)生。
std::packaged_task就介紹到這里,下一篇將會(huì)完成本次異步運(yùn)行的整體脈絡(luò),將std::async和std::future一起介紹結(jié)大家。
附: C++11多線程中的樣例代碼的編譯及運(yùn)行
g++ -std=c++11 <Your Cpp File>
./a.out
| 上一篇 C++11多線程-promise |
目錄 | 下一篇: C++11多線程-future+async |
|---|