多线程绘图

一.问题的提出
      微软资源管理器一般来说应该算是一个非常优秀的软件,但是其微软资源的图像浏览功能应该来说是有些问题的(可能是我的windows版本比较老(windows2000),且不说JPEG的BUG,就他采用的单线程读图应该就算是不合理之处,为什么这么说,我们可以做个试验,首先构造一个BMP图像,图像应尽可能大一点,我选择了6146*2048的24位色的图片,大约36M多,当点击此图片后,系统将非常缓慢的显示此图片的预览图,而且无法快速切换至其他文件(可能是我的机器太慢了),很明显微软在这里没有采用多线程处理,系统将把图像的所有数据读入内存后才进行处理显示,这就容易造成读取大图像文件的死机.因为在读取文件的过程中用户是无法中断的. 如何能快速切换文件呢? 一般对于一个需要较长时间处理数据的程序,用户可能希望在处理过程中提前中断当前的数据操作,我们有几种方式处理:
      1.采用单线程处理方法:
       这种方法一般采用的方法是在数据处理的过程中加入
       MSG message;
       if (::PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) {
           ::TranslateMessage(&message); 
           ::DispatchMessage(&message);
       }
      等语句,如果发生用户中断或其他消息,则系统执行用户中断或其他消息相应的消息处理程序,消息处理完成后,程序再返回原来数据处理过程继续执行相关的代码. 这种方式的优点就是编程简单,只要在程序中设置一个全局变量,在用户中断消息中改变这个变量,数据处理时判断如果变量改变则退出处理即可.在一般情况下这种办法还是比较有效的,但是这种方法也有些问题,比如在这种情况下,你的鼠标按住窗口不停的拖动,系统就不会运行数据处理直到你松开鼠标按钮.由于数据处理很容易被系统的消息所干扰,采用这种方法在实际程序的运行过程中会使人感觉程序不是很流畅. 
       2.采用多线程处理方法 这里我们可以将文件分为界面线程和工作线程,工作线程负责读取文件并发送读取进度和读取内容到界面线程,而界面线程负责界面的显示和用户切换文件后,结束工作线程.需要指出的是在目前的单CPU+WINDOWS的环境下采用多线程进行数据,并不能提高数据的处理速度(超线程CPU除外),他所能提高的是界面的反应速度,即界面的友好程度.当然这种友好程度也会被一些其他程序过多的占用CPU所干扰.

二.如何使用多线程
   1.demo界面设置:
   为了能很好的模拟测试多线程绘图显示程序,我们建立了一个简单的demo程序,
先使用mfc向导,建立一个对话框程序,并增加全局变量CDib m_dib,对话框上建立三
个按钮和一个进度条,按钮1的功能为将c:/test1.bmp读入m_dib,按钮2的功能为将
c:/test2.bmp读入m_dib,按钮3的功能为终止m_dib的读入(关于CDib将在以后详细
说明),进度条用于显示当前读入数据的进度,于是按钮中的代码如下:
   void CDemoDlg::OnButton1()
   {
      m_dib.LoadBmp ("c://test1.bmp");
   }
   void CDemoDlg::OnButton2()
   {
      m_dib.LoadBmp ("c://test2.bmp");
   }
   void CDemoDlg::OnButton3()
   {
 m_dib.DoStop();   //停止CDib的读入操作
   }
   但是,m_dib读入功能为多线程读取函数那有可能在读取test1.bmp时,程序要求读取test2.bmp,
所以按钮中的代码修改如下:
   void CDemoDlg::OnButton1()
   {
      m_dib.DoStop();       //这也可以直接加入LoadBmp中
      m_dib.LoadBmp ("c://test1.bmp");
   }
   void CDemoDlg::OnButton2()
   {
      m_dib.DoStop();
      m_dib.LoadBmp("c://test2.bmp");
   }

   当然我们还要显示m_Dib,为了显示m_Dib我们对OnPaint作如下修改:
   void CDemoDlg::OnPaint()
   {
 CPaintDC dc(this);
 CRect clientRc;
 GetClientRect(&clientRc);          //取得窗体大小

 CDC memDC1;                        //我们将读入的图像置于memDC1中
 CDC memDC2;                        //memDC2设置背景图像

 CBitmap m_bitmap;
 CBitmap * m_pOldBitmap;
 CBitmap * m_pOldBitmapA;

 memDC1.CreateCompatibleDC(&dc);
 memDC2.CreateCompatibleDC(&dc);

 m_bitmap.CreateCompatibleBitmap(&dc,clientRc.Width() , clientRc.Height());
        m_pOldBitmapA = memDC2.SelectObject(&m_bitmap);
 m_pOldBitmap  = memDC1.SelectObject(&m_Dib);  //m_Dib为全局变量

 memDC2.BitBlt(0,0, clientRc.Width(), clientRc.Height(),
              &memDC1,0,0, SRCCOPY);       //将读入的图像复制到背景图中

 dc.BitBlt(0,0, clientRc.Width(), clientRc.Height(),
              &memDC2,0,0, SRCCOPY);       //显示当前图像
 
        memDC2.SelectObject(m_pOldBitmapA );
        memDC1.SelectObject(m_pOldBitmap );
   }
   我们建立两个消息函数负责子线程向界面线程报告完成情况和状态,
   ON_MESSAGE(WM_SETPOS, ON_WM_SETPOS)            //负责子线程向主线程报告完成进度
 ON_MESSAGE(WM_INTERRUPT, ON_WM_INTERRUPT)      //负责子线程向主线程报告是否中断

 

在CDib中我们每次读入图像的一行,然后发送WM_SETPOS消息给界面线
程,界面则刷新当前图像和进度条,在ON_WM_SETPOS中,wParam为bmp总行
数,lParam为目前处理的行数,为了简化程序我们只处理自底向上(即bmp图
像高度>0)的图像.
   void CDemoDlg::ON_WM_SETPOS(WPARAM wParam, LPARAM lParam)
   {
     CRect clientRc;
     GetClientRect(&clientRc);   //取得当前窗体大小

     CProgressCtrl *cw;
     cw=(class CProgressCtrl *)GetDlgItem(IDC_PROGRESS1);
     cw->SetRange (0,100);       //设置进度条

     float l;
     if (wParam!=0)
         l=(float) lParam/(float)wParam*100;
     else
         l=0;
    cw->SetPos ((int)l);       //更新进度条

    CRect rc;
    rc.SetRect (0,wParam-lParam,clientRc.Width(),wParam-lParam+1);
    InvalidateRect(&rc,false); //刷新当前行
   }
   至此我们的界面程序基本完成.

   2.CDib设置:
   下面我们进行CDib的构建,为了方便图像的显示处理我们继承CBitmap构建CDib,
为了解说方便,我们只处理24位色图,这里我们给出CDib基本结构,其他部分读者可以
自由扩充.

   class CDib : public CBitmap 
   {
    public:
             CDib();                            //构造函数
            virtual ~CDib();                //析构函数
            BYTE * m_lpBits;            //数据指针,指向cbitmap的数据区
            LPBITMAPINFOHEADER m_lpBmih;//图像头指针
            HANDLE hMutex;          //互斥变量
            HANDLE hStopEvent;    //线程读到此信号,立即退出现有的操作
            CWnd * m_Wnd;            //用于子线程发送消息界面指针
            CWinThread  *m_cAniThread;  //多线程中,子线程对象
            virtual void DoStop();      //用于界面线程中停止当前线程
            BOOL LoadBmp(LPCTSTR lpszPathName, CDC* pDC = NULL);
                                                   //由bmp读入图像
            static UINT ThreadProcRead(LPVOID parm);
                                                  //多线程函数读入图像
            .
            .
            .
    protected:
            BOOL AttachDib(HGDIOBJ hObject);
                                                 //Attach Object
            BOOL DeleteDib();      //delete object
            void Initialize();             //设置图像头等操作
            .
            .
            .
   }

你可能感兴趣的:(windows)