[ATL/WTL]_[初级]_[Win32实现Cocoa的dispatch_async到主线程的异步消息处理]

场景

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((char*)bsd->data_str);
            free(bsd);
        }];
    });

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():6880
helloworld m_dwMainThreadID: 4836

参考

Windows 消息循环初解
dispatch_async

下载

vs2010项目下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Peter(阿斯拉达)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值