复制文件的最佳方式是什么,以便我可以在复制过程中轻松取消复制?



我正在使用ReadFileEx从文件中读取一些字节,并使用WriteFileEx将一些字节写入设备。此操作将重复,直到所有文件字节都被读取并写入设备。

我使用 Ex API 的原因是,通过向操作系统请求重叠的 IO,可以在读/写函数执行任务时保持 UI 线程响应更新进度条。

该过程从 ReadFileEx 和MY_OVERLAPPED结构开始,ReadCompletionRoutine将一起传入。读取完成后,将调用读取完成例程。在例程中,将发出 WriteFileEx,并调用 WriteCompletionRoutine。在写入完成例程中,在将MY_OVERLAPPED结构的偏移量重置到下一个位置后,将发出另一个 ReadFileEx。也就是说,一旦完成读取或写入,两个完成将相互调用。

请注意,仅当调用线程处于可警报状态时,才会执行上述进程。我使用 while 循环通过不断检查全局状态变量是否设置为 TRUE 来保持线程可警报。完成所有过程后,状态变量completed将在ReadCompletionRoutine内部设置为 TRUE。

仅供参考,MY_OVERLAPPED结构是一个继承OVERLAPPPED结构的自定义结构,以便我可以向其添加所需的 2 个信息。

现在,我的问题是我想添加一个取消功能,以便用户可以取消所有已启动的进程。我所做的很简单。当按下取消按钮时,我将completed变量设置为 TRUE,因此 while 循环将中断并停止可警报状态,因此不会执行完成例程。但是,我不知道如何取消Read/WriteFileEx发送的重叠请求及其完成例程以及MY_OVERLAPPED结构(请参阅代码中的//******* 部分(。现在,一旦按下取消按钮,我的代码就会崩溃。取消部分是导致崩溃的部分。请帮忙,谢谢。

//In MyClass.h========================================
struct MY_OVERLAPPED: OVERLAPPED {
MyClass *event;
unsigned long long count;
};

//In MyClass.cpp - main===============================
MY_OVERLAPPED overlap;
memset(&overlap, 0,sizeof(overlap));
//point to this class (MyClass), so all variables can later be accessed
overlap.event = this; 
//set read position
overlap.Offset = 0;
overlap.OffsetHigh = 0;
overlap.count = 0;
//start the first read io request, read 524288 bytes, which 524288 bytes will be written in ReadCompletionRoutine
ReadFileEx(overlap.event->hSource, overlap.event->data, 524288, &overlap, ReadCompletionRoutine);
while(completed != true) {
updateProgress(overlap.count);
SleepEx(0,TRUE);
}
//********
CancelIo(overlap.event.hSource);
CancelIo(overlap.event.hDevice);
//********

//In MyClass.cpp - CALLBACKs===============================  
void CALLBACK ReadCompletionRoutine(DWORD errorCode, DWORD bytestransfered, LPOVERLAPPED lpOverlapped)
{
//type cast to MY_OVERLAPPED
MY_OVERLAPPED *overlap = static_cast<MY_OVERLAPPED*>(lpOverlapped);
//write 524288 bytes and continue to read next 524288 bytes in WriteCompletionRoutine
WriteFileEx(overlap->event->hDevice, overlap->event->data, 524288, overlap, WriteCompletionRoutine);
}
void CALLBACK WriteCompletionRoutine(DWORD errorCode, DWORD bytestransfered, LPOVERLAPPED lpOverlapped)
{
MY_OVERLAPPED *overlap = static_cast<MY_OVERLAPPED*>(lpOverlapped);
if(overlap->count<fileSize/524288) {
//set new offset to 524288*i, i = overlap->count for next block reading
overlap->count = (overlap->count)+1;
LARGE_INTEGER location;
location.QuadPart = 524288*(overlap->count);
overlap->Offset = location.LowPart;
overlap->OffsetHigh = location.HighPart;
ReadFileEx(overlap->event->hSource, overlap->event->data, 524288, overlap, ReadCompletionRoutine);
}
else {
completed = TRUE;
}
}

请注意,我不喜欢使用多线程编程。除此之外,任何实现相同目标的更好方法都是值得赞赏的。请随时提供详细的代码和解释。谢谢。

我实际上为此使用后台线程,因为现代C++使这变得非常容易。 当然,这比你现在要做的要容易得多。 因此,请尝试摆脱您可能拥有的任何先入之见,即这对您来说是错误的方法,请尝试本着其意图的精神阅读这篇文章。 谢谢。

首先,这里有一些非常简单的概念验证代码,您可以自己编译并运行以试用。 乍一看,这可能看起来有点"那又怎样?",但请耐心等待,我将在最后解释:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <thread>
#include <chrono>
#include <memory>
#include <atomic>
int usage ()
{
std::cout << "Usage: copy_file infile outfilen";
return 255;
}
void copy_file (FILE *infile, FILE *outfile, std::atomic_bool *cancel)
{
constexpr int bufsize = 32768;
std::unique_ptr <char []> buf (new char [bufsize]);
std::cout << "Copying: ";
while (1)
{
if (*cancel)
{
std::cout << "nCopy cancelled";
break;
}
size_t bytes_read = fread (buf.get (), 1, bufsize, infile);
if (bytes_read == 0)
{
// Check for error here, then break out of the loop
break;
}
size_t bytes_written = fwrite (buf.get (), 1, bytes_read, outfile);
// Again, check for error etc
std::cout << ".";
}
std::cout << "nCopy completen";
// Now probably something like PostMessage here to alert your main loop hat the copy is complete
}
int main (int argc, char **argv)
{
if (argc < 3) return usage ();
FILE *infile = fopen (argv [1], "rb");
if (infile == NULL)
{
std::cout << "Cannot open input file " << argv [1] << "n";
return 255;
}
FILE *outfile = fopen (argv [2], "wb");
if (outfile == NULL)
{
std::cout << "Cannot open output file " << argv [2] << "n";
fclose (infile);
return 255;
}
std::atomic_bool cancel = false;
std::thread copy_thread = std::thread (copy_file, infile, outfile, &cancel);
std::this_thread::sleep_for (std::chrono::milliseconds (200));
cancel = true;
copy_thread.join ();   // waits for thread to complete
fclose (infile);
fclose (outfile);      // + error check!
std::cout << "Program exitn";
}

当我在我的机器上运行它时,我得到类似的东西:

background_copy_test bigfile outfile
Copying: 
.....................................................................................
..............
Copy cancelled
Copy complete
Program exit

那么,这有什么值得注意的呢?好吧,没有特别的顺序:

  • 这很简单。
  • 这是标准C++。 那里根本没有Windows调用(我故意这样做,试图说明一个观点(。
  • 这是万无一失的。
  • 它"只是工作"。

当然,现在,当您在现实生活中复制文件时,您不会让主线程进入睡眠状态。 不 不 不。 相反,您只需通过std::thread启动副本,然后放置"复制..."。带有"取消"按钮的对话框(大概,这将是一个模式对话框(

然后:

  • 如果按下该按钮,只需将cancel设置为true,魔术就会发生。
  • 完成后,copy_file向"复制"对话框发送WM_APP+nnn消息。 它也可以这样做,让对话框更新其进度条(我把所有这些东西都留给你(。
  • 在你破坏copy_thread之前,不要忽略对join()的呼唤,否则它就会超出范围!

还有什么? 好吧,为了正确理解这个问题,请学习一些现代C++。 CPP偏好是一个有用的网站,但你真的应该读一本好书。 然后,您应该能够将此处学到的经验教训应用于您的特定用例。

编辑:我想说的是,您最好在"复制"对话框的WM_INITDIALOG处理程序中创建线程。 然后,您可以将对话框的HWND传递给copy_file,以便它知道将这些消息发送到何处。 只是一个想法。

如果你想从这篇文章中获利,你有很多阅读工作要做。 但话又说回来,你应该这样做。 我担心这篇文章将一事无成。 羞耻。

最新更新