MFC里面多线程的使用

总结:所有与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控件绘出来,然后我们可以控制绘图的动作,可以暂停可以放大缩小等。

 

程序下载

MFC里面多线程的使用,布布扣,bubuko.com

MFC里面多线程的使用

上一篇:java实现插入排序


下一篇:JavaScript高级程序设计——学习笔记(基本概念 一)