📄 teach_sp_52.htm
字号:
printf("耗时 %d\n",GetTickCount()-dwBegin);
//操作完成
CloseHandle(hFile);
delete pbRead;
delete pbBuf;
}
}
</pre>
在文件的异步读写中,如果ReadFile/WriteFile返回FALSE,则需要通过LastError来进一步确认是否发生错误,例如下面的错误检测代码:
<pre>
bResult = ReadFile(hFile, &inBuffer, nBytesToRead, &nBytesRead,
&gOverlapped) ;
if (!bResult)
switch (dwError = GetLastError())
{
case ERROR_HANDLE_EOF:
{
//到达文件尾
}
case ERROR_IO_PENDING:
{
//正在进行异步操作
} // end case
} // end switch
} // end if
此外可以通过GetOverlappedResult函数来得到异步函数的执行情况
BOOL GetOverlappedResult(
HANDLE hFile, // handle to file, pipe, or device
LPOVERLAPPED lpOverlapped, // overlapped structure
LPDWORD lpNumberOfBytesTransferred, // bytes transferred
BOOL bWait // wait option
);
</pre>
如果函数调用返回FALSE则可以用GetLastError来得到错误,如果返回成功则可以通过lpNumberOfBytesTransferred参数来确定当前有多少数据已经被读或写。lpOverlapped参数必须与调用ReadFile或WriteFile时使用同一个数据区。最后一个参数bWait表明是否等待异步操作结束时才返回,如果设置为TRUE就可以等待文件读写完成时返回,否则就会马上返回,利用这个特点可以利用它来等待异步文件操作的结束(就如同等待事件变为有信号状态一样起到相同的作用)。
<p>在上面的例子中没有过多的进行错误检查,如果大家有兴趣可以自己运行一下这个例子,看看异步文件读写对性能的影响。在我自己的计算机PII 450 IBM 30GB硬盘上同步读比异步读慢了大约10%左右,这主要时因为数据处理时间我设置为两秒钟,如果设置得足够长,会显示出异步和同步处理时的差异极限。此外由于磁盘缓存得作用也会影响结果,所以如果读入的数据量更大将会产生更明显的差异,这是因为虽然异步读写会在调用等待函数上也会耗费一些时间,所以如果数据量小就无法分辨出差异。
<p>请记住OVERLAPPED参数在文件操作执行完以前不要手工修改结构内的值,因为系统会使用结构中的数据。
<p>对于WriteFile操作也可以用相同方法,在WriteFile中磁盘操作将耗费更多的时间,所以使用异步写更能体现优势,你可以将这个例子改为磁盘写后自己测试一下。
<p><a href=sam_sp_52_1.zip>下载利用ReadFile进行异步文件读的示范代码</a>
<p>第二种方法,利用ReadFileEx/WriteFileEx,这对函数使用回调函数来进行读写完成的通知。
<pre>
BOOL ReadFileEx(
HANDLE hFile, // handle to file
LPVOID lpBuffer, // data buffer
DWORD nNumberOfBytesToRead, // number of bytes to read
LPOVERLAPPED lpOverlapped, // offset
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // completion routine
);
</pre>
<ul><li>hFile为文件句柄。
<li>lpBuffer指明了写入数据的内存区指针。
<li>nNumberOfBytesToRead为要求读入的数据字节数。
<li>lpOverlapped为一个OVERLAPPED的结构,这个结构hEvent字段将被系统忽略,但是通过Offset和OffsetHigh字段来表明开始读文件的位置。
<li>lpCompletionRoutine为一个通知用的回调函数。
</ul>
函数的最后一个参数指明了一个回调函数,这个回调函数称为一个告警函数。函数必须具有这样的原型:
<pre>
VOID CALLBACK FileIOCompletionRoutine(
DWORD dwErrorCode, // completion code
DWORD dwNumberOfBytesTransfered, // number of bytes transferred
LPOVERLAPPED lpOverlapped // I/O information buffer
);</pre>
<ul><li>dwErrorCode为错误代码,如果为0表示正确,为ERROR_HANDLE_EOF表示到达文件的末尾。
<li>dwNumberOfBytesTransfered为成功传送的字节数,如果发生错误,这个值为0。
<li>lpOverlapped为一个OVERLAPPED的结构,这个结构必须与调用ReadFileEx时指向相同的数据区,并且在调用ReadFileEx后不能手工更改这个结构中的字段。
</ul>
<p>那么如何检测回调函数已经被调用了(文件操作已经完成),你可以设置一个全局的同步量来进行通知,但是系统提供了其他简便的方法供开发者使用,这就是SleepEx WaitForMultipleObjectsEx 和WaitForSingleObjectEx。
<p>当线程调用ReadFileEx或WriteFileEx时,提供了一个告警函数,这个告警函数会被放入一个队列,当操作完成时操作系统会从队列中取出这些函数进行调用。所以对于属于本进程内部所产生的需要等待被调用的告警函数来说可以直接使用SleepEx对当前线程进行休眠并等待当前提交所有的告警函数被执行。如果希望等待指定文件操作上的告警函数被调用你可以使用WaitForMultipleObjectsEx或WaitForSingleObjectEx。这三个函数的原型为:
<pre>
DWORD SleepEx(
DWORD dwMilliseconds, // time-out interval
BOOL bAlertable // early completion option
);
DWORD WaitForSingleObjectEx(
HANDLE hHandle, // handle to object
DWORD dwMilliseconds, // time-out interval
BOOL bAlertable // alertable option
);
DWORD WaitForMultipleObjectsEx(
DWORD nCount, // number of handles in array
CONST HANDLE *lpHandles, // object-handle array
BOOL fWaitAll, // wait option
DWORD dwMilliseconds, // time-out interval
BOOL bAlertable // alertable option
);
</pre>
这三个函数和Sleep WaitForSingleObject WaitForMultipleObjects的差别就在于多了最后一个参数bAlertable,这个参数需要设置为TRUE表明等待文件异步操作的完成。通过检测函数返回值可以得知文件操作是否完成,例如下面的代码:
<pre>
ReadFileEx(hFile,pbRead,1024*1024*50,&overlap,MyIORoutine);
while(WAIT_IO_COMPLETION != SleepEx(1,TRUE) )//检测文件操作是否完成
//while (WaitForSingleObjectEx(hFile,1,TRUE) != WAIT_OBJECT_0 )
//在这里WaitForSingleObjectEx和SleepEx具有相同作用
{
DoSomething();
}
</pre>
对于SleepEx来说如果返回WAIT_IO_COMPLETION则表示异步操作完成,而对于文件对象来说如果异步操作完成文件对象就会变为有信号状态。下面的例子是一个利用告警回调函数实现的文件异步读写。
<pre>
VOID CALLBACK MyIORoutine(
DWORD dwErrorCode, // completion code
DWORD dwNumberOfBytesTransfered, // number of bytes transferred
LPOVERLAPPED lpOverlapped // I/O information buffer
)
{//定义一个简单的回调函数
printf("文件读完成\n");
}
void DoSomething(void)
{
printf("current time %d\n",GetTickCount());
Sleep(2000);//假设耗时的操作需要两秒钟
}
//下面是使用异步读的示范代码,假设c:\temp\large_file.dat文件有130MB大小()
//一次性读入50MB字节,在读入的过程中进行一些其他操作
void ReadM(void)
{
HANDLE hFile = CreateFile("c:\\temp\\large_file.dat",GENERIC_READ,0,
NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED|FILE_ATTRIBUTE_NORMAL,NULL);
if( INVALID_HANDLE_VALUE != hFile )
{
BYTE *pbRead = new BYTE[1024*1024*50];//50MB字节
OVERLAPPED overlap;
overlap.Offset = 0;
overlap.OffsetHigh =0;
overlap.hEvent = NULL; //使用告警函数时无需要使用事件
DWORD dwBegin= GetTickCount();//记录开始时间
printf("begin time %d\n",dwBegin);
ReadFileEx(hFile,pbRead,1024*1024*50,&overlap,MyIORoutine);
while(WAIT_IO_COMPLETION != SleepEx(1,TRUE) )//检测文件操作是否完成
//while (WaitForSingleObjectEx(hFile,1,TRUE) != WAIT_OBJECT_0 )
//在这里WaitForSingleObjectEx和SleepEx具有相同作用
{
DoSomething();//在文件读的执行过程中进行其他操作
}
printf("耗时 %d\n",GetTickCount()-dwBegin);
//操作完成
CloseHandle(hFile);
delete pbRead;
}
}
</pre>
<p>WriteFileEx的用法与ReadFileEx的用法是类似的。
<p><a href=sam_sp_52_2.zip>下载利用ReadFileEx进行异步文件读的示范代码</a>
<p>在磁盘操作中磁盘写比读需要花更多的时间,并且大文件的异步写可以更加有效的提高CPU利用率。但是异步操作会给开发和调试带来一些麻烦,所以我建议除非在非常必要(性能要求非常高,文件非常大)的情况下才使用异步的磁盘读写。再提一点,对于磁盘文件的异步操作的方式同样可以用于上章所讲的命名管道,或者是串口的异步操作。
<a name=lock></a><p>文件加锁时在打开文件后对文件的某个区域加锁,加锁后可以防止其他进程对该区域数据进行读取。相关的函数为:
<pre>
BOOL LockFile(
HANDLE hFile, // 文件句柄
DWORD dwFileOffsetLow, // 文件加锁开始位置低32位
DWORD dwFileOffsetHigh, // 文件加锁开始位置高32位
DWORD nNumberOfBytesToLockLow, // 区域长度低32位
DWORD nNumberOfBytesToLockHigh // 区域长度高32位
);
BOOL UnlockFile(
HANDLE hFile, // 文件句柄
DWORD dwFileOffsetLow, // 文件解锁开始位置低32位
DWORD dwFileOffsetHigh, // 文件解锁开始位置高32位
DWORD nNumberOfBytesToLockLow, // 区域长度低32位
DWORD nNumberOfBytesToLockHigh // 区域长度高32位
);
</pre>
在文件加锁和解锁上需要有对应关系,这种对应关系就是对A区域加锁后必须对A区域解锁后才可以对其他区域解锁,而且必须是一对一的关系,也就是说调用一次对A区域的加锁函数就必须调用一次对A区域的解锁函数,而不能对一个区域加锁后分次对该区域的不同部分解锁。
<p>在MFC中对文件操作进行了封装,CFile中封装了各种文件操作。在CFile中常用的成员函数有以下这些:
<pre>
CFile( LPCTSTR lpszFileName, UINT nOpenFlags ); //打开文件
virtual BOOL Open( LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL ); //打开文件
uOpenFlags为打开文件时的参数,可以取的以下值的组合:
CFile::modeRead / CFile::modeReadWrite / CFile::modeWrite 读写模式
CFile::modeCreate 创建文件
CFile::shareDenyNone / CFile::shareDenyRead / CFile::shareDenyWrite 共享设置
CFile::typeText / CFile::typeBinary 以文本形式还时二进制形式打开文件
virtual void Close( ); //关闭文件
virtual UINT Read( void* lpBuf, UINT nCount ); //读文件
virtual void Write( const void* lpBuf, UINT nCount ); // 写文件
virtual LONG Seek( LONG lOff, UINT nFrom ); //设置文件指针
void SeekToBegin( );//移动文件指针到文件头
DWORD SeekToEnd( );//移动文件指针到文件尾
virtual void LockRange( DWORD dwPos, DWORD dwCount ); //锁定文件
virtual void UnlockRange( DWORD dwPos, DWORD dwCount ); //解锁文件
</pre>
<p>CStdioFile是CFile的派生类,主要是完成对文本文件的操作,它只有两个成员函数:
<pre>
BOOL ReadString(CString& rString); //读入文件中一行
void WriteString( LPCTSTR lpsz );//将字符串作为一行写入文件
</pre>
<p>
<p align=center><a href=index.htm#charpter5>返回</a></p>
<p align=center><small>版权所有 闻怡洋 <a href=http://www.vchelp.net/><font class=engul>http://www.vchelp.net/<font></font></font></a></small></p>
</small>
</td></tr><!-- article content end-->
</table>
</td>
</tr><!-- article title end-->
</table>
</td></tr></table><!--整体框架 END-->
<!-- LANGUAGE='JavaScript'>write_tail();<-->
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -