一、用辅助工作线程实现文档保存
1、线程知识
MFC程序中的线程有两种形式:用户界面线程和辅助工作线程,本文所讲述的是辅助工作线程的添加和使用方法。
上述两种形式的线程都是用系统全局函数AfxBeginThread添加的,该函数有两种形式。
2、辅助工作线程的使用
当使用AfxBeginThread添加辅助工作线程时,我们使用第一个重载函数形式,至少需要给函数提供两个参数,第一个参数即参数表中的第一个参数,该参数是用户自己定义的工作线程处理函数的函数名;第二个参数即参数表中的第二个参数,该参数作为AfxBeginThread函数参数表中第二个参数的同时也是用户自己定义的工作线程处理函数参数表中的唯一参数,也就是说,用户自定义工作线程处理函数参数表中只含有一个LPVOID类型的参数,该参数即为AfxBeginThread函数参数表中的第二个参数。有人可能会问:如果要传递的参数多于一个怎么办?答案是:这一参数通常是一个LPVOID类型的结构(struct),而该结构所能传递的参数则是任意的。下面举出一个结构文件的例子,其中还包括辅助工作线程处理函数的声明,该函数的返回类型为UINT。这是因为:线程可以将返回值0作为线程结束的标志而使用。为了便于理解,本代码做了简化处理。
3、文档保存函数的重写
如多线程编程之使用工作线程实现文档自动保存(I)正文以及FAQ中所述,上述OnSaveDocument函数并不是从CDocument类中继承过来的,而是Hoops(三维显示引擎)中的HoopsDoc类继承过来,并经过重写,重写后的函数原形如下所示。
为了方便初学者理解,本文省略了对于由用户操作和自动保存的并发因素造成的文档保存格式错误的解决方案,主要原因是该方案针对的是CAD系统的大规模模型的保存,不太具有普适性,对读者的帮助不大。
二、使用Timer计时器
在这里,我们使用Timer计时器来定时触发辅助工作线程,以完成保存动作。
1、声明并设定Timer计时器
在设定Timer计时器之前,首先要声明一个计时器成员变量,也就是给计时器取个名字,该变量应是一个由CWnd基类所派生的类(如CView类和CFrameWnd类,因为只有CWnd基类的派生类才能产生WM_TIMER系统消息)的成员变量,它要在整个类中生存,而不是某个函数内部。
Timer计时器声明与设定形式如下:
2、捕捉WM_TIMER系统消息以触发计时器处理函数
为已经添加Timer计时器的CWnd派生类添加WM_TIMER消息处理函数OnTimer(UINT nIDEvent),以捕捉WM_TIMER消息并处理计时器触发事件。在函数中我们要添加辅助工作线程开启代码,以实现用辅助工作线程实现文档自动保存。代码如下:
3、关闭Timer计时器与重设Timer计时器
当用户进行定制,改变自动保存的间隔时间时,就需要关闭现有计时器,并重设一个Timer计时器。要关闭计时器,使用KillTimer(m_nTimer)函数即可,其中的m_nTimer为想要关掉的计时器变量。相关代码如下:
三、总结与展望
本文中,对辅助工作线程的使用方法和Timer计时器的使用方法做了一些简要的介绍,所举代码并不是完整的程序代码,虽不能独立运行,但足以说明上述技术的使用原理与方法,不足之处还望读者见谅,并与我讨论!
1、线程知识
MFC程序中的线程有两种形式:用户界面线程和辅助工作线程,本文所讲述的是辅助工作线程的添加和使用方法。
上述两种形式的线程都是用系统全局函数AfxBeginThread添加的,该函数有两种形式。
CWinThread* AfxBeginThread(
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
CWinThread* AfxBeginThread(
CRuntimeClass* pThreadClass,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
CWinThread* AfxBeginThread(
CRuntimeClass* pThreadClass,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
2、辅助工作线程的使用
当使用AfxBeginThread添加辅助工作线程时,我们使用第一个重载函数形式,至少需要给函数提供两个参数,第一个参数即参数表中的第一个参数,该参数是用户自己定义的工作线程处理函数的函数名;第二个参数即参数表中的第二个参数,该参数作为AfxBeginThread函数参数表中第二个参数的同时也是用户自己定义的工作线程处理函数参数表中的唯一参数,也就是说,用户自定义工作线程处理函数参数表中只含有一个LPVOID类型的参数,该参数即为AfxBeginThread函数参数表中的第二个参数。有人可能会问:如果要传递的参数多于一个怎么办?答案是:这一参数通常是一个LPVOID类型的结构(struct),而该结构所能传递的参数则是任意的。下面举出一个结构文件的例子,其中还包括辅助工作线程处理函数的声明,该函数的返回类型为UINT。这是因为:线程可以将返回值0作为线程结束的标志而使用。为了便于理解,本代码做了简化处理。
//AutoSaveThrdProc.h
///////////////////////////////////////
//这是辅助工作线程处理函数所要用到的结构参数的声明
struct CAutoSaveThrdInfo
{
CHoopsDoc *pPartDoc;
CHoopsView *pPartView;
};
UINT AutoSaveThrdProc(LPVOID lPARAM);
辅助工作线程处理函数的实现如下所示,为了便于理解,本代码同样做了一些必要的简化处理。///////////////////////////////////////
//这是辅助工作线程处理函数所要用到的结构参数的声明
struct CAutoSaveThrdInfo
{
CHoopsDoc *pPartDoc;
CHoopsView *pPartView;
};
UINT AutoSaveThrdProc(LPVOID lPARAM);
// AutoSaveThrdProc.cpp: implementation of the CAutoSaveThrdProc class.
#include "stdafx.h"
#include "solid.h"
#include "AutoSaveThrdProc.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
UINT AutoSaveThrdProc(LPVOID lPARAM)
{
CAutoSaveThrdInfo *AutoSaveThrdInfo = (CAutoSaveThrdInfo *)lPARAM;
CString pathname= "c:\\";
CString filename;
CHoopsDoc *pPartDoc = AutoSaveThrdInfo -> pPartDoc;
filename = pPartDoc ->GetTitle();
if(filename.Right(4)==".typ")
pathname += filename;
else
pathname += filename + ".typ";
pPartDoc -> OnSaveDocument(pathname);
pPartDoc = NULL;
return 0;
}
#include "stdafx.h"
#include "solid.h"
#include "AutoSaveThrdProc.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
UINT AutoSaveThrdProc(LPVOID lPARAM)
{
CAutoSaveThrdInfo *AutoSaveThrdInfo = (CAutoSaveThrdInfo *)lPARAM;
CString pathname= "c:\\";
CString filename;
CHoopsDoc *pPartDoc = AutoSaveThrdInfo -> pPartDoc;
filename = pPartDoc ->GetTitle();
if(filename.Right(4)==".typ")
pathname += filename;
else
pathname += filename + ".typ";
pPartDoc -> OnSaveDocument(pathname);
pPartDoc = NULL;
return 0;
}
3、文档保存函数的重写
如多线程编程之使用工作线程实现文档自动保存(I)正文以及FAQ中所述,上述OnSaveDocument函数并不是从CDocument类中继承过来的,而是Hoops(三维显示引擎)中的HoopsDoc类继承过来,并经过重写,重写后的函数原形如下所示。
BOOL CPartDoc::OnSaveDocument(LPCTSTR lpszPathName)
{
CWaitCursor cursor;
// 检测文件是否出错
HPartModel *pModel = (HPartModel *)m_pHoopsModel;
HPART *pHpart = pModel->GetHpart();
// 找到文件名
CString szFileName = lpszPathName;
int nPos = szFileName.ReverseFind('\\');
int nLength = szFileName.GetLength();
CString szTemp = szFileName.Right( nLength - nPos - 1 );
szTemp.SetAt(szTemp.GetLength() - 4,'\0');
// Model存储相关处理
//保存临时Modal文件
pModel->OnSaveDocument( szTemp );
// 结构化存储文件
CPartFile cOpenFile(lpszPathName, CPartFile::hrFILEWRITE);
cOpenFile.SetPart(pHpart);
BOOL OK = cOpenFile.WritePart();
if ( OK )
{
// 压缩文件
CString szZipPathName(lpszPathName) ;
szZipPathName = szZipPathName.Left(szZipPathName.ReverseFind('.')) ;
// getting information about the date and the attributes
// (this is new in ZipFunc)
CZipFile zf(szZipPathName, 0);
zip_fileinfo zi;
CFile f(lpszPathName, CFile::modeRead);
zf.UpdateZipInfo(zi, f);
zf.OpenNewFileInZip(szTemp, zi, Z_BEST_COMPRESSION);
char buf[1024];
int size_read;
do
{
size_read = f.Read(buf, 1024);
if (size_read)
zf.WriteInFileInZip(buf, size_read);
}
while (size_read == 1024);
// cannot be called by the destructor because it may throw an exception
// (it is not good to throw an exception when another one may be progress)
zf.Close();
f.Close();
// 压缩文件覆盖掉原文件
if( !CopyFile(szZipPathName,lpszPathName,FALSE) ){
tyMessageBox("存储文件失败!");
}
else{
CFile::Remove(szZipPathName);
}
}
SetModifiedFlag( FALSE );
return OK;
}
上述代码中调用了Hoops的临时文件保存函数,该函数经过重写,与主界面线程不存在依赖关系,所以可以被辅助工作线程调用。下半部分代码则是将临时Modal文件进行压缩,并用其替换临时文件。{
CWaitCursor cursor;
// 检测文件是否出错
HPartModel *pModel = (HPartModel *)m_pHoopsModel;
HPART *pHpart = pModel->GetHpart();
// 找到文件名
CString szFileName = lpszPathName;
int nPos = szFileName.ReverseFind('\\');
int nLength = szFileName.GetLength();
CString szTemp = szFileName.Right( nLength - nPos - 1 );
szTemp.SetAt(szTemp.GetLength() - 4,'\0');
// Model存储相关处理
//保存临时Modal文件
pModel->OnSaveDocument( szTemp );
// 结构化存储文件
CPartFile cOpenFile(lpszPathName, CPartFile::hrFILEWRITE);
cOpenFile.SetPart(pHpart);
BOOL OK = cOpenFile.WritePart();
if ( OK )
{
// 压缩文件
CString szZipPathName(lpszPathName) ;
szZipPathName = szZipPathName.Left(szZipPathName.ReverseFind('.')) ;
// getting information about the date and the attributes
// (this is new in ZipFunc)
CZipFile zf(szZipPathName, 0);
zip_fileinfo zi;
CFile f(lpszPathName, CFile::modeRead);
zf.UpdateZipInfo(zi, f);
zf.OpenNewFileInZip(szTemp, zi, Z_BEST_COMPRESSION);
char buf[1024];
int size_read;
do
{
size_read = f.Read(buf, 1024);
if (size_read)
zf.WriteInFileInZip(buf, size_read);
}
while (size_read == 1024);
// cannot be called by the destructor because it may throw an exception
// (it is not good to throw an exception when another one may be progress)
zf.Close();
f.Close();
// 压缩文件覆盖掉原文件
if( !CopyFile(szZipPathName,lpszPathName,FALSE) ){
tyMessageBox("存储文件失败!");
}
else{
CFile::Remove(szZipPathName);
}
}
SetModifiedFlag( FALSE );
return OK;
}
为了方便初学者理解,本文省略了对于由用户操作和自动保存的并发因素造成的文档保存格式错误的解决方案,主要原因是该方案针对的是CAD系统的大规模模型的保存,不太具有普适性,对读者的帮助不大。
二、使用Timer计时器
在这里,我们使用Timer计时器来定时触发辅助工作线程,以完成保存动作。
1、声明并设定Timer计时器
在设定Timer计时器之前,首先要声明一个计时器成员变量,也就是给计时器取个名字,该变量应是一个由CWnd基类所派生的类(如CView类和CFrameWnd类,因为只有CWnd基类的派生类才能产生WM_TIMER系统消息)的成员变量,它要在整个类中生存,而不是某个函数内部。
Timer计时器声明与设定形式如下:
UINT_PTR m_nTimer;
//计时器时间表示以毫秒(ms)为单位
m_nTimer = SetTimer(起始时间,触发时间,NULL);
上述起始时间和触发时间之间的间隔即触发器的触发间隔,例如m_nTimer = SetTimer(0,5000,NULL)表示计时器每5秒时间间隔发送一个WM_TIMER消息。该程序中,我们将Timer计时器的声明与设定放在CPartView类的初始化函数(注意:不是构造函数)中,使得计时器在初始化CPartView对象时一同被创建。//计时器时间表示以毫秒(ms)为单位
m_nTimer = SetTimer(起始时间,触发时间,NULL);
2、捕捉WM_TIMER系统消息以触发计时器处理函数
为已经添加Timer计时器的CWnd派生类添加WM_TIMER消息处理函数OnTimer(UINT nIDEvent),以捕捉WM_TIMER消息并处理计时器触发事件。在函数中我们要添加辅助工作线程开启代码,以实现用辅助工作线程实现文档自动保存。代码如下:
void CPartView::OnTimer(UINT nIDEvent)
{
//创建并启动自动保存工作线程
CWinThread *pThread;
CHoopsDoc *pDoc = CHoopsView::GetDocument();
if(m_bAutoSaveAllowed&&pDoc->IsModified())// &&(!m_bAutoSaveThrdExist))
{
pThread = AfxBeginThread(AutoSaveThrdProc,&m_AutoSaveThrdInfo);
m_bAutoSaveThrdExist = TRUE;
}
CHoopsView::OnTimer(nIDEvent);
}
{
//创建并启动自动保存工作线程
CWinThread *pThread;
CHoopsDoc *pDoc = CHoopsView::GetDocument();
if(m_bAutoSaveAllowed&&pDoc->IsModified())// &&(!m_bAutoSaveThrdExist))
{
pThread = AfxBeginThread(AutoSaveThrdProc,&m_AutoSaveThrdInfo);
m_bAutoSaveThrdExist = TRUE;
}
CHoopsView::OnTimer(nIDEvent);
}
3、关闭Timer计时器与重设Timer计时器
当用户进行定制,改变自动保存的间隔时间时,就需要关闭现有计时器,并重设一个Timer计时器。要关闭计时器,使用KillTimer(m_nTimer)函数即可,其中的m_nTimer为想要关掉的计时器变量。相关代码如下:
LRESULT CPartView::OnResetTimer(WPARAM wPARAM, LPARAM lPARAM)
{
KillTimer(m_nTimer);
m_nTimer = CHoopsView::SetTimer(1,m_AutoSaveInterval,NULL);
return LRESULT();
}
{
KillTimer(m_nTimer);
m_nTimer = CHoopsView::SetTimer(1,m_AutoSaveInterval,NULL);
return LRESULT();
}
三、总结与展望
本文中,对辅助工作线程的使用方法和Timer计时器的使用方法做了一些简要的介绍,所举代码并不是完整的程序代码,虽不能独立运行,但足以说明上述技术的使用原理与方法,不足之处还望读者见谅,并与我讨论!
为了彻底解决多线程之间对同一资源的同步使用所出现的冲突问题,我将在下一篇文章中讲解如何使用线程同步类来实现文档的多线程自动保存。敬请关注!