描述
- 程序开始时,在
UI
显示之前,需要做一次网络请求,请求到的数据用于界面上一些内容的展示- 这属于是一个一次性任务
- 除了一开始这个一次性任务之外,当
UI
选择了某个逻辑后,还会弹出另外一个新的界面- 需要创建订单,需要显示该订单对应的二维码
- 在订单创建后,需要对该订单进行一定时间的轮询,查询订单状态
- 要做到这些功能,也可以用一次性任务的思维去实现(比如往线程池里抛任务,按任务类型,创建订单任务,完成后再抛一个查询订单状态,查询过期,再创建,再查询)
外层一个死循环,一直在这样的事情,内部按一次性任务来 - 但是这里实现,我把他们放到了一起(创建完订单,就在同一个线程里开始查询)
- 总的来说,这属于一个长期任务
一次性任务
std::async
- 对于一次性任务,这里是用
std::async
启动了一个异步任务来处理
1 2 3 4 5 6 7 8 9 10 11 |
void CMainWnd::OnWndCreate() { if (!IsUserMarkedChared()) { ft_ = std::async(std::launch::async, [this] { if (task_helper_) { return task_helper_->start_task(); } }); } // ... } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
retention::status CTaskHelper::start_task() { HANDLE hStopEv = ::CreateEvent(NULL, FALSE, FALSE, L""); if (!hStopEv) { return retention::status::config_create_event_failed; } CString url = CONFIG_URL + Utils::GetProductPid(); CStringA strResponse; BOOL bRet = SyncHttpGet(url, strResponse, 1000, hStopEv); if (bRet == FALSE) { if (hStopEv) { CloseHandle(hStopEv); } return retention::status::config_request_failed; } if (hStopEv) { CloseHandle(hStopEv); } CAutoDeleteJson root_json(cJSON_Parse(strResponse)); if (root_json.pJson == NULL) { return retention::status::config_json_parse_failed; } int nError = JsonHelper::GetInt(root_json.pJson, "errno", 1); if (nError != 0) { return retention::status::config_json_nerror0_failed; } cJSON* pDataJson = cJSON_GetObjectItem(root_json.pJson, "data"); if (!pDataJson) { return retention::status::config_json_parse_failed; } CString reason1 = JsonHelper::GetStringT(pDataJson, "reason1", "需要付费才能用", CP_UTF8); CString reason2 = JsonHelper::GetStringT(pDataJson, "reason2", "会员价格高", CP_UTF8); CString reason3 = JsonHelper::GetStringT(pDataJson, "reason3", "没有我想要的壁纸", CP_UTF8); CString reason4 = JsonHelper::GetStringT(pDataJson, "reason4", "广告多", CP_UTF8); float money = JsonHelper::GetDouble(pDataJson, "wan_money", 1.0); int month = JsonHelper::GetInt(pDataJson, "wan_month", 1); if (delegate_) { delegate_->OnComplete(reason1, reason2, reason3, reason4, money, month); } return retention::status::config_request_success; } |
- 并在真正展示界面之前,调用
future
的get
函数来获取结果- 该函数先上报一些选项
- 调用
future
的get
函数来获取结果 - 判断是否展示后续界面(用户是否被标记,网络请求是否成功)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
LRESULT CMainWnd::OnRetentionUnstBtn(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& /*bHandled*/) { CStatistic::GetInstance()->SendStatTypeAction(L"Optimize", L"uninstall_continue"); std::wstring res; bool flag_checked = false; if (checkbox_reason1_->IsChecked()) { flag_checked = true; res += reason1_.GetBuffer(); res += L"|"; } if (checkbox_reason2_->IsChecked()) { flag_checked = true; res += reason2_; res += L"|"; } if (checkbox_reason3_->IsChecked()) { flag_checked = true; res += reason3_; res += L"|"; } if (checkbox_reason4_->IsChecked()) { flag_checked = true; res += reason4_; res += L"|"; } if (checkbox_other_->IsChecked()) { flag_checked = true; res += retention_edit_.GetInstallPath(); } if (flag_checked) { CStatistic::GetInstance()->SendStatTypeAction(L"Optimize", L"uninstall_feedback"); } // 上报选项 if (!res.empty()) { IHttpTask* pHttpTask = CHttpTaskFactory::GetInstance()->CreateTask(); if (pHttpTask) { retention_submit_task_.reset(pHttpTask, HttpTaskDeletor()); retention_submit_task_->SetCallback(this); retention_submit_task_->PostStr("reason", CT2A(res.c_str(), CP_UTF8)); retention_submit_task_->Start(eHttpPost, UPLOAD_REASON); task_id_ = retention_submit_task_->GetID(); } } auto status = ft_.get(); if (!CanShowRententionWnd() || status != retention::status::config_request_success) { UninstallFromRetention(); return 0; } ShowWindow(SW_HIDE); retention_edit_.Show(SW_HIDE); m_oRetentionWnd->SetPreWnd(m_hWnd); m_oRetentionWnd->Active(); m_dwCurWnd = RETENTION_WND; return 0; } |
- 代码中没体现的一点:
ft_
是作为类的成员变量,所以按理,应该在类的析构的时候,对ft_
做如下处理- 但是由于上面的逻辑,可以保证对
get
的调用,所以ft_
在析构函数里也就不需要处理了 - 如果需要处理,但没有处理会怎么样?
如果任务是死循环,那么最后阻塞在析构函数那里
因为在类的析构里,最后会调用future
的析构,而ft_
的析构函数会隐式阻塞
如果任务是耗时任务,同理也会阻塞一段时间,直到任务完成 - 案例见下文
1 2 3 |
if (ft_.valid()) { ft_.get(); } |
长期任务
线程只创建一次
- 用原子变量来实现
- 当然也可以用
std::call_once
- 当然也可以用
1 |
std::atomic<bool> is_wx_started{false}; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void CMainWnd::OnStartWxPay() { if (!is_wx_started.exchange(true)) { ft_wx_ = std::async( std::launch::async, [this](float money) { while (!stop_flag_.load(std::memory_order_relaxed)) { if (task_helper_) { task_helper_->create_pay(stop_flag_, money, CString(L"weixin")); } } }, retention_money_); } source_.Empty(); source_ = L"weixin"; } |
std::async
- 由于是单个的长期任务,所以用
std::async
就可以了
future
的隐式阻塞问题
- 上面讲到的future的隐式阻塞问题,就是在这里发现的,具体如下:
- 一开始
future
用的局部变量,对于这里的两个future
,没有调用get
- 发现会有隐式阻塞问题,所以改成成员变量,解决问题
- 并且在析构的时候,统一调用了
- 一开始
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
void CTaskHelper::create_pay(std::atomic<bool>& is_stop, float money, CString source) { HANDLE hStopEv = ::CreateEvent(NULL, FALSE, FALSE, L""); if (!hStopEv) { if (delegate_) { delegate_->OnFailed(retention::status::pay_create_event_failed); } return; } CString url = PAY_URL; url.Append(_T("?money=")); url += std::to_string(money).c_str(); url.Append(_T("&source=")); url.Append(source); url.Append(_T("&channel=")); url += Utils::GetProductPid(); CStringA strResponse; BOOL bRet = SyncHttpGet(url, strResponse, 1000, hStopEv); if (bRet == FALSE) { if (hStopEv) { CloseHandle(hStopEv); } if (delegate_) { delegate_->OnFailed(retention::status::pay_request_failed); } return; } do { CAutoDeleteJson root_json(cJSON_Parse(strResponse)); if (root_json.pJson == NULL) { if (delegate_) { delegate_->OnFailed(retention::status::pay_json_parse_failed); } break; } int nError = JsonHelper::GetInt(root_json.pJson, "errno", 1); if (nError != 0) { if (delegate_) { delegate_->OnFailed(retention::status::pay_json_nerror0_failed); } break; } cJSON* pDataJson = cJSON_GetObjectItem(root_json.pJson, "data"); if (!pDataJson) { if (delegate_) { delegate_->OnFailed(retention::status::pay_json_parse_failed); } break; } CString url = JsonHelper::GetStringT(pDataJson, "url", "", CP_UTF8); CString base64img = JsonHelper::GetStringT(pDataJson, "base64_img", "", CP_UTF8); CString token = JsonHelper::GetStringT(pDataJson, "token", "", CP_UTF8); if (delegate_) { delegate_->OnPayReqComplete(url, token, base64img, source); } int count = 0; do { if (is_stop.load(std::memory_order_relaxed)) { break; } CString url = QUERY_URL; url.Append(_T("?token=")); url += token; CStringA strResponse; BOOL bRet = SyncHttpGet(url, strResponse, 1000, hStopEv); if (bRet == FALSE) { if (delegate_) { // delegate_->OnFailed(retention::status::pay_request_failed); } count++; continue; } do { CAutoDeleteJson root_json(cJSON_Parse(strResponse)); if (root_json.pJson == NULL) { if (delegate_) { // delegate_->OnFailed(retention::status::pay_json_parse_failed); } break; } int nError = JsonHelper::GetInt(root_json.pJson, "errno", 1); if (nError != 0) { if (delegate_) { // delegate_->OnFailed(retention::status::pay_json_nerror0_failed); } break; } cJSON* pDataJson = cJSON_GetObjectItem(root_json.pJson, "data"); if (!pDataJson) { if (delegate_) { // delegate_->OnFailed(retention::status::pay_json_parse_failed); } break; } CString status = JsonHelper::GetStringT(pDataJson, "status", ""); if (status.Compare(L"0") != 0) { if (delegate_) { delegate_->OnQuerryComplete(); } if (hStopEv) { CloseHandle(hStopEv); } return; } } while (false); count++; if (count < QUERYCOUNT) { std::this_thread::sleep_for(std::chrono::seconds(WAIT_SECOND)); } } while (count < QUERYCOUNT); if (hStopEv) { CloseHandle(hStopEv); } } while (false); } |
1 2 3 4 5 6 7 8 |
void CMainWnd::OnQuerryComplete() { std::call_once(init_flag_, [this] { if (m_oRetentionWnd) { m_oRetentionWnd->ShowWindow(SW_HIDE); m_oRetentionWnd->SendMessageW(QUERY_SUCC); } }); } |
1 2 3 4 5 6 7 8 9 10 11 |
CMainWnd::~CMainWnd(void) { // stop_flag_.store(true, std::memory_order_release); if (ft_wx_.valid()) { ft_wx_.get(); } if (ft_zfb_.valid()) { ft_zfb_.get(); } // ... } |
- 然后,发现当启动多个订单线程的情况,一个订单完成时退出了,另一个订单还在进行,因为都是死循环,而这个另一个订单根本没扫过码,所以一直不会查到状态改变,所以会一直死循环,不会退出
- 用于在两个启动异步任务的地方都用了原子变量
stop_flag_
,解决了问题 - 当一个订单完成时,会设置这个原子变量,如下
- 这样两个线程就能正确退出了
- 但是,这一步的实现,并没有将
stop_flag_
作为参数传给create_pay
- 用于在两个启动异步任务的地方都用了原子变量
1 2 3 4 5 |
// err while(true) { // ... } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void CMainWnd::OnStartWxPay() { if (!is_wx_started.exchange(true)) { ft_wx_ = std::async( std::launch::async, [this](float money) { while (!stop_flag_.load(std::memory_order_relaxed)) { if (task_helper_) { task_helper_->create_pay(stop_flag_, money, CString(L"weixin")); } } }, retention_money_); } source_.Empty(); source_ = L"weixin"; } |
1 2 3 4 5 6 7 8 9 10 11 12 |
if (!is_zfb_started.exchange(true)) { ft_zfb_ = std::async( std::launch::async, [this](float money) { while (!stop_flag_.load(std::memory_order_relaxed)) { if (task_helper_) { task_helper_->create_pay(stop_flag_, money, CString(L"zfb")); } } }, retention_money_); } |
1 2 3 4 5 6 7 8 9 10 11 12 |
void CMainWnd::OnRententionWndQuerySucc() { if (m_oRetentionWnd) { m_oRetentionWnd->DeActive(); } stop_flag_.store(true, std::memory_order_release); if (m_oRetentionSuccWnd) { m_oRetentionSuccWnd->SetPreWnd(m_oRetentionWnd->m_hWnd); m_oRetentionSuccWnd->Active(); } } |
- 然后,又遇到了问题:
- 在上面的描述里讲过,这里的实现方式是在一个大循环(死循环)里面,还会有一个小循环,可能长达好几分钟
- 所以,当上面通过原子变量解决了这两个异步任务的退出问题后,发现,当主界面结束后,进程还会存在很长时间才会退出
- 将
stop_flag_
作为参数传给create_pay
,在create_pay
内部的小循环中,对这个变量进行了判断,如下(详细见上): - 于是解决了这个问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// ... do { if (is_stop.load(std::memory_order_relaxed)) { break; } CString url = QUERY_URL; url.Append(_T("?token=")); url += token; // ... } while( /*many time*/) // ... |
1 2 3 4 5 6 7 8 |
void CMainWnd::OnQuerryComplete() { std::call_once(init_flag_, [this] { if (m_oRetentionWnd) { m_oRetentionWnd->ShowWindow(SW_HIDE); m_oRetentionWnd->SendMessageW(QUERY_SUCC); } }); } |
总结
std::future
- 用
std::async
启动的异步线程任务,要对future
调用get
- 否则会发送
future
的隐式阻塞(阻塞与否看任务是否死循环)
声明:本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!