[ATL/WTL]_[初级]_[Win32实现Cocoa的dispatch_async到主线程的异步消息处理]
来源:互联网 发布:什么叫网络建设与管理 编辑:程序博客网 时间:2024/05/18 03:28
场景
1.写界面程序时, 大多是底层部分需要至少一个工作线程来处理逻辑, 避免使用主线程导致界面卡顿, 当底层线程处理数据完毕后, 需要转发到主线程绘制数据. 因为非绘图线程绘制数据会导致不可预料的问题, 一般情况下会导致程序莫名其妙崩溃,多线程同时调用绘制函数会导致资源冲突,而且冲突可能会在特定情况下才发生,不易察觉.
2.工作线程发送数据到主线程,Win32消息处理一般有几种方式 PostMessage
,SendMessage
,PostThreadMessage
, 或者自己实现的信号量通讯.
3.一般情况下我们通过PostMessage发送消息, 指定自定义消息类型, 之后传数据指针到WPARAM, 类型给LPARAM, 之后在接收的窗口类里进行捕抓和处理, 通过LPARAM来进行 switch 处理, 这样坏处就是
– 处理代码都放到统一的地方, 代码量增大时会造成维护困难.
– 需要写一个枚举集合来存储处理类型, 便于分开处理不同的业务逻辑.
– 需要封装数据对象,由于不同的业务处理涉及不同的数据对象, 可能会需要创建很多不同的数据结构.
– 业务相关的逻辑被强制分离, 导致不便于追踪和调试.
说明
1.macOS 的Cocoa提供了一种Grand Central Dispatch
方式来异步出来逻辑, 还支持lambda表达式, 让业务逻辑更有序, 便于程序员逐步跟踪, 可惜Win32没有提供那么方便的技术, 不过不妨碍我们写一个. 像下边这种的需要打开一个窗口选择文件或更新某个进度条,某个按钮最常见.
BASObserveData *bsd = (BASObserveData*)malloc(sizeof(BASObserveData));*bsd = *data;dispatch_async(dispatch_get_main_queue(), ^(void){ NSOpenPanel *panel = [NSOpenPanel openPanel]; [panel setPrompt:@"Confirm"]; [panel setCanChooseDirectories:NO]; [panel setCanCreateDirectories:NO]; [panel setCanChooseFiles:YES]; [panel setAllowsMultipleSelection:YES]; [panel setMessage:@"Please select backup file(s)."]; [panel beginSheetModalForWindow:[NSApp mainWindow] completionHandler:^(NSInteger result) { if (result == NSFileHandlingPanelOKButton) { NSArray* pathUrls= [[panel URLs] retain]; bsd->send_userdata = pathUrls; bsd->id = kCommCommandBookImportToJson; bsd->data_int = MyCommProgressTypeDevice; [UiNotificationCenter SendCommand:LIBCOMM_COMMAND_DO_BOOLK setBASObserveData:bsd]; } free(bsd); free((char*)bsd->data_str); }]; });
2.我们可以模仿 dispatch_async(dispatch_get_main_queue(), ^(void){});
写一个Win32的实现, 我用的vs2010, 不完善的C++11, 目前还不支持异步lambda表达式, 所以使用函数代替, 如果项目一开始就使用以下的方式, 的确会少很多bug和精简代码. 这里我使用了std::function和std::bind技术来实现。
例子
1.头文件 dispatch_queue.h
#ifndef __DISPATCH_QUEUE_H#define __DISPATCH_QUEUE_H#include <Windows.h>#include <WinUser.h>#include <functional>#include <atlbase.h>#include <atlapp.h>enum{ WMC_DISPATCH_MAIN_QUEUE = WM_USER+1000};typedef struct DispatchQueueObject1{ DWORD threadId; HWND m_hwnd;}DispatchQueueObject;extern void DispatchQueueInit(HWND hwnd);extern DispatchQueueObject* DispatchGetMainQueue();// vs2010 可能支持 带捕获方式的lambda 表达式里, 捕获的变量在异步执行 lambda 时会无效.template<class DispatchFunction> void DispatchAsync2(DispatchQueueObject* queue,DispatchFunction func){ std::function<void()>* callback = new std::function<void()>(func); if(queue->threadId){ ::PostThreadMessage(queue->threadId, WMC_DISPATCH_MAIN_QUEUE,(WPARAM)callback,0); }else{ ::PostMessage(queue->m_hwnd, WMC_DISPATCH_MAIN_QUEUE,(WPARAM)callback,0); }}inline void DispatchAsync(DispatchQueueObject* queue,std::function<void()>* callback){ if(queue->threadId){ ::PostThreadMessage(queue->threadId, WMC_DISPATCH_MAIN_QUEUE,(WPARAM)callback,0); }else{ ::PostMessage(queue->m_hwnd, WMC_DISPATCH_MAIN_QUEUE,(WPARAM)callback,0); }}#endif
2.dispatch_queue.cpp
#include "stdafx.h"#include "dispatch_queue.h"static HWND gMainFrameHwnd = NULL;void DispatchQueueInit(HWND hwnd){ gMainFrameHwnd = hwnd;}DispatchQueueObject* DispatchGetMainQueue(){ DispatchQueueObject* object = (DispatchQueueObject*)malloc(sizeof(DispatchQueueObject)); memset(object,0,sizeof(DispatchQueueObject)); object->m_hwnd = gMainFrameHwnd; return object; }
3.在进入循环前调用 DispatchQueueInit(wndMain.m_hWnd);
int Run(LPTSTR /*lpstrCmdLine*/ = NULL, int nCmdShow = SW_SHOWDEFAULT){ CMessageLoop theLoop; _Module.AddMessageLoop(&theLoop); CMainFrame wndMain; if(wndMain.CreateEx() == NULL) { ATLTRACE(_T("Main window creation failed!\n")); return 0; } DispatchQueueInit(wndMain.m_hWnd); wndMain.ShowWindow(nCmdShow); int nRet = theLoop.Run(); _Module.RemoveMessageLoop(); return nRet;}
4.在主窗口注册接收消息处理函数 MESSAGE_HANDLER(WMC_DISPATCH_MAIN_QUEUE, OnDispatchMainQueueEvent)
, 使用例子看 OnFileNew
. 运行程序点击菜单 File->New
// MainFrm.h : interface of the CMainFrame class///////////////////////////////////////////////////////////////////////////////#pragma once#include "dispatch_queue.h"#include <iostream>#include <string>#include <sstream>#include <assert.h>void PrintStr(std::wstring* str){ assert(_Module.m_dwMainThreadID == ::GetCurrentThreadId()); std::wstringstream wss; wss << *str << L" m_dwMainThreadID: " << _Module.m_dwMainThreadID << L"\n"; std::wstring str1 = wss.str(); OutputDebugString(str1.c_str()); delete str;}DWORD WINAPI StartThread(void* data){ std::wstringstream wss; wss << L"work GetCurrentThreadId():" << GetCurrentThreadId() << L"\n"; std::wstring str1 = wss.str(); OutputDebugString(str1.c_str()); auto str = new std::wstring(L"helloworld"); auto callback = new std::function<void()>(std::bind(PrintStr,str)); DispatchAsync(DispatchGetMainQueue(),callback); return 0;}class CMainFrame : public CFrameWindowImpl<CMainFrame>, public CUpdateUI<CMainFrame>, public CMessageFilter, public CIdleHandler{public: DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME) CDispatch_async_testView m_view; virtual BOOL PreTranslateMessage(MSG* pMsg) { if(CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg)) return TRUE; return m_view.PreTranslateMessage(pMsg); } virtual BOOL OnIdle() { return FALSE; } BEGIN_UPDATE_UI_MAP(CMainFrame) END_UPDATE_UI_MAP() BEGIN_MSG_MAP(CMainFrame) MESSAGE_HANDLER(WMC_DISPATCH_MAIN_QUEUE, OnDispatchMainQueueEvent) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit) COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew) COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout) CHAIN_MSG_MAP(CUpdateUI<CMainFrame>) CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>) END_MSG_MAP()// Handler prototypes (uncomment arguments if needed):// LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)// LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)// LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/) LRESULT OnDispatchMainQueueEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { std::function<void()>* func = (std::function<void()>*)wParam; (*func)(); delete func; return 0; } LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { m_hWndClient = m_view.Create(m_hWnd, rcDefault, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, WS_EX_CLIENTEDGE); // register object for message filtering and idle updates CMessageLoop* pLoop = _Module.GetMessageLoop(); ATLASSERT(pLoop != NULL); pLoop->AddMessageFilter(this); pLoop->AddIdleHandler(this); return 0; } LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { // unregister message filtering and idle updates CMessageLoop* pLoop = _Module.GetMessageLoop(); ATLASSERT(pLoop != NULL); pLoop->RemoveMessageFilter(this); pLoop->RemoveIdleHandler(this); bHandled = FALSE; return 1; } LRESULT OnFileExit(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { PostMessage(WM_CLOSE); return 0; } LRESULT OnFileNew(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { // TODO: add code to initialize document DWORD IDThread2; HANDLE h2 = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE) StartThread,NULL,0,&IDThread2); return 0; } LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { CAboutDlg dlg; dlg.DoModal(); return 0; }};
输出
work GetCurrentThreadId():6880helloworld m_dwMainThreadID: 4836
参考
Windows 消息循环初解
dispatch_async
下载
vs2010项目下载
- [ATL/WTL]_[初级]_[Win32实现Cocoa的dispatch_async到主线程的异步消息处理]
- [ATL/WTL]_[初级]_[Win32窗口自定义消息处理过程]
- [ATL/WTL]_[初级]_[SDI程序关闭的注意事项]
- [ATL/WTL]_[初级]_[常用的界面对象操作]
- [ATL/WTL]_[初级]_[Windows消息循环初解]
- [ATL/WTL]_[初级]_[拖放文件到窗口]
- [Cocoa]_[初级]_[使用异步线程加载图片文件实现进度条的更新]
- [ATL/WTL]_[初级]_[环境搭建]
- [ATL/WTL]_[初级]_[关于CEdit不识别WM_KEYDOWN的问题]
- [ATL/WTL]_[初级]_[关于graphics.DrawImage绘图时显示不正常的问题]
- [ATL/WTL]_[初级]_[转发消息让CStatic支持点击消息操作]
- [Cocoa]_[初级]_[NSThread的使用]
- [ATL/WTL]_[初级]_[窗口如何实现WM_MOUSELEAVE和WM_MOUSEHOVER]
- [ATL/WTL]_[初级]_[选择目录对话框]
- [Cocoa]_[初级]_[Cocoa入门级界面开发之界面初级程序编辑的步骤和实现方法]
- [Cocoa]_[初级]_[自定义的界面上绘制图片]
- [Cocoa]_[初级]_[vcf文件的生成和导出]
- [Cocoa]_[初级]_[如何绘制带颜色的文本]
- NodeInstaller
- Maven详解
- 晶体管 放大电路的 分析
- mvc,mvp,mvvm图示详解
- UDP转TCP隧道工具udptunnel
- [ATL/WTL]_[初级]_[Win32实现Cocoa的dispatch_async到主线程的异步消息处理]
- 如何保持你的App中的字体大小不随手机系统的字体大小变化而变化
- 前端构建工具gulpjs的使用介绍及技巧
- java ssl socket流程
- 联表查多表多条数据的最新一条 partition by
- Java
- “金融科技价值——数据驱动金融商业裂变”主论坛精彩纷呈,看企业大佬们眼中的Fintech2.0新时代
- “2017金融科技·数据驱动金融商业裂变价值峰会”圆满落幕,数据猿携手金融产业精英探索行业新方向
- python应用系列教程——python中ftp操作:连接、登录、获取目录,重定向、上传下载,删除更改