总结:所有与UI(user interface)的操作都集中在主执行线程中,其他的 纯种运算工作 才交给Work threads 去做。
注意,多执行线程并不能让程序执行得比较快(除非是在多CPU 机器上,并且使用支持 symmetric multiprocessing 的操作系统),只是能够让程序比较「有反应」。
正确态度
什么是使用多执行线程的好时机呢?如果你的程序有许多事要忙,但是你还要随时保持注
意某些外部事件(可能来自硬件或来自使用者),这时就适合使用多执行线程来帮忙。
以通讯程序为例。你可以让主执行线程负责使用者接口,并保持中枢的地位。而以一个分
离的执行线程处理通讯端口
下面介绍下自己的写的这个小demo吧!
原则:共享数据有两种方式,一种是通过全局变量;一种是通过自定义消息。下面讲讲自定义消息这种方法。
普通线程中(work thread)发送自定义消息到UI线程,在UI线程中的响应函数去操作UI界面上的控件。
千万不要在普通线程中去直接操作界面上的控件,(一般普通线程主要做一些运算,死循环操作,几乎和UI不打什么交道)
在xxxxDlg.h文件中的操作:
1、在窗口类的开头添加自定义消息号
一般是#define WM_MY_MESSAGE WM_USER + 1
2、添加自定义消息响应函数
afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);
如果我们是通过按钮等控件添加响应函数的话,线程启动函数就不需要了,如果没有的话,就需要自己添加了。
添加如下:
private: // 假设这个函数启动线程 void StartThread();
3、假如我们需要在开启新线程的时候需要传递数据的话,假如是简单的基本类型,我们直接带过去就可以了,在此,全局的一般变量直接写,很容易。但假如是,需要带过去的数据很多,我们此时就必须要对数据进行封装了,比如说二个二维数组,一个二维数组等。这时就需要封装了。对了还有一点,假如我们需要传递的是这个对话框里面的实例变量,此时我们可以通过引用的方式来对其进行绑定,当然这也涉及结构体的概念。结构体是我们封装东西的好帮手。当然,我们在线程处理函数里面也可以绑定数据给消息响应函数。
typedef struct data
{
int m_XYDist1[2][381];
} Data;
4、添加消息映射
BEGIN_MESSAGE_MAP(CReadFileDlg, CDialogEx) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDC_BUTTON1, &CReadFileDlg::OnBnClickedButton1) ON_WM_TIMER() ON_BN_CLICKED(IDC_BUTTON2, &CReadFileDlg::OnBnClickedButton2) ON_BN_CLICKED(IDC_BUTTON3, &CReadFileDlg::OnBnClickedButton3) ON_BN_CLICKED(IDC_BUTTON4, &CReadFileDlg::OnBnClickedButton4) // 添加消息映射,这样做以后通过WM_MY_MESSAGE,就可以找到OnMessage
ON_MESSAGE(WM_MY_MESSAGE,OnMyMessage) END_MESSAGE_MAP()
5、开启一个新的线程,点击按钮,添加响应函数。
void CReadFileDlg::OnBnClickedButton1()
{
AfxBeginThread(ThreadProc,this);
}
注:通过传递this,指代实例化的对象,通过此我们就可以操作对话框类里面的一切属性和方法了。当然我们在用的时候,需要强制类型转换成我们需要的形式。如下所示:
6、编写线程处理函数
1: //在线程函数内发送消息给主窗口
2: UINT ThreadProc(LPVOID p)
3: {
4: CReadFileDlg* pDlg = (CReadFileDlg*)p;
5: FILE *infile = fopen("data.txt","rb");
6: Data data;
7: //初始化各变量
8: //其实这也是初始化,这里已经分配内存,但并未初始化为0.
9: RaderInfor *prader = new RaderInfor;//用new就已经初始化过了??答案是没有初始化
10: memset(prader,0,sizeof(RaderInfor));
11:
12: while(!feof(infile))
13: {
14: fseek(infile,sizeof(RaderInfor),SEEK_CUR);
15: fread(prader,sizeof(RaderInfor),1,infile);//这里相当于执行memcpy();一样,相当于复制。
16: memset(pDlg->m_DIST,0,sizeof(pDlg->m_DIST));
17: for(int i = 0; i < 381; i++)
18: if( abs(prader->dist[i]*1.0) < MaxDistPlay)
19: pDlg->m_DIST[i] = prader -> dist[i];
20: else
21: pDlg->m_DIST[i] = 60000;
22:
23: for(int i=0; i<381; ++i)
24: {
25: float theta = (i*0.5-5) / 180 * 3.1415; //角度换算。380/0.5=190;前5°要了。
26: float x = cos( theta ) * pDlg->m_DIST[i]; //也就是说,m_DIST[i]为R。
27: float y = sin( theta ) * pDlg->m_DIST[i];
28:
29: pDlg->m_XYDist[0][i] = -x;
30: data.m_XYDist1[0][i] = -x;
31: pDlg->m_XYDist[1][i] = y;
32: data.m_XYDist1[1][i] = y;
33: }
34: // 发送消息给窗体,第一个参数是雷达坐标信息
35: Sleep(50); //程序错在没有给主程序足够的时间显示,也就是说,我们必须先让子线程休息会,然后让主线程工作
36: pDlg ->SendMessage(WM_MY_MESSAGE,(WPARAM)&data,0);
37: // pDlg->DrawData();
38: //pDlg->SendServer();
39: }
40: fclose(infile);
41: return 0;
42: }
上面程序中,SendMessage的参数和OnMyMessage消息映射里面的一样,也就是说,程序通过WM_MY_MESSAGE,自定义消息号,将两者联系起来。
注:1)上面的程序通过一个结构体data,给消息响应函数传递了参数。
2)通过上面的sleep(50)给UI线程留有了时间,注意,这个很重要。
其实我们可以把我写的这段程序分成三个线程来理解,可能会好点。一个是画图的线程,一个是文件解析的线程,一个是主线程,主要响应一下按钮控件的操作。然后有一个反馈。
7、编写消息响应函数OnMessage()
1: //消息处理函数 主窗口响应消息控制进度条控件
2: LRESULT CReadFileDlg::OnMyMessage(WPARAM wParam, LPARAM lParam)
3: {
4: if(GoOn) //第一次我把这个放在了线程里面,其实用处不大,它在绘之前的程序,我们现在在主UI中
5: //进行更新,必须在主UI中进行绘制
6: {
7: Data* data = ((Data*)wParam); //将从普通线程中传递过来的数据进行解析。我们传递的是指针。属于地址传递。
8: //曲线轮廓显示
9: CDC* pDC = m_Picture.GetDC(); //通过GetDc()获取的HDC直接与相关设备沟通,
10: CDC memDC;
11: CBitmap bmp; //本函数创建的DC,则是与内存中的一个表,面相关联。
12: memDC.CreateCompatibleDC(pDC); //该函数创建一个与指定设备兼容的内存设备上下文环境(DC)。
13: bmp.CreateCompatibleBitmap( pDC, PicW, PicH); //该函数创建与指定的设备环境相关的设备兼容的位图
14: memDC.SelectObject(bmp); //该函数选择一对象到指定的设备上下文环境中,该新对象替换先前的相同类型的对象
15:
16: memDC.SelectStockObject(WHITE_BRUSH);//该语句把终端字体选入设备环境
17: memDC.SelectStockObject(WHITE_PEN);
18:
19: pDC->SelectStockObject(WHITE_BRUSH);
20: pDC->SelectStockObject(WHITE_PEN);
21:
22: int yGap = 10;
23: memDC.Ellipse( PicW/2-5, PicH-yGap-5, PicW/2+5, PicH-yGap+5); //小圆形,用来标识雷达
24: int x,y;
25:
26: int init=0;
27: CString a;
28: for(int i=0; i<381; ++i)
29: {
30: x = (PicW/2.0) +( data->m_XYDist1[0][i] / PicXS);
31: y = PicH-yGap - ( data->m_XYDist1[1][i] / PicYS);
32: memDC.Ellipse(x-2,y-2,x+2,y+2);
33: memDC.SetPixel( x, y, RGB(255,0,0));
34: }
35: pDC->StretchBlt(0,0,PicW,PicH,&memDC,0,0,PicW,PicH,SRCCOPY); //函数从源矩形中复制一个位图到目标矩形
36: bmp.DeleteObject(); //必要时按目标设备设置的模式进行图像的拉伸或压缩
37:
38: /*目标区域左上角点的x坐标
39: 目标区域左上角点的y坐标
40: 目标区域的宽度
41: 目标区域的高度
42: 源贴图区域DC的指针
43: 源贴图区域x坐标
44: 源贴图区域y坐标
45: 像素直接拷贝模式*/
46: memDC.DeleteDC();
47: ReleaseDC(pDC);
48: }
49: return 0;
50: }
程序源码:读取文件并解析,然后将解析完的数据通过picture控件绘出来,然后我们可以控制绘图的动作,可以暂停可以放大缩小等。