2

我有一个正在开发的文件管理器应用程序。这些文件通常为 500MB 到 2GB。一切正常,但应用程序“停止响应”非常烦人。我想做的是在每个读/写操作之后逐个字节或逐个 meg 复制其中一些 Application.DoEvents() 。沿着这些思路,我不知道要使用的实际类是什么,所以我只是要编一些东西:)

private void CopyFile(string inFilename, string outFilename)
{
    FileReader inReader(inFilename);
    FileWriter outWriter(outFilename, FileMode.OpenOrCreate);

    byte theByte;
    while (theByte = inReader.ReadByte())
    {
        outWriter.WriteByte(theByte, WriteMode.Append);
        UpdateProgressBar();
        Application.DoEvents();
    }

    inReader.CloseFile();
    outWriter.CloseFile();
}

我知道这似乎应该是一件简单的事情,但在我的一生中,如果不使用直接 API 调用或其他方式,我似乎找不到任何类型的例子来做到这一点。我必须在这里遗漏一些东西,所以如果有人能让我在这里走上正确的轨道,我将非常感激。

提前致谢!

4

8 回答 8

6

您应该在表单上使用BackgroundWorker来进行复制。它将允许在单独的线程上完成文件复制,并让您的 UI 响应。有一些额外的复杂性,但 BackgroundWorker 会为您处理很多管道。但是,有很多你想做例子

于 2009-05-10T01:22:12.463 回答
3

您需要使用BackgroundWorkerThread来完成此操作。这是一个很好的例子:使用后台工作线程复制文件

于 2009-05-10T01:23:09.327 回答
3

我想使用该CopyFileEx功能。如果托管框架库中不存在该功能的类似物,那么谷歌无论如何如何使用它:也许像http://www.thetechscene.com/2008/09/copyfileex-with-progress-callback这样的文章-in-c-使用-pinvoke/

我想使用的原因CopyFileEx是我假设它是在 O/S 内核中实现的,数据在文件系统驱动程序中从一个文件复制到另一个文件,而不使用用户内存(更不用说托管内存)。

于 2009-05-10T01:37:13.053 回答
1

Threading.ThreadPool.QueueUserWorkitem 应该让您顺利进行。

于 2009-05-10T01:22:26.630 回答
1

一种方法是在单独的线程中执行复制操作。当线程执行复制文件的工作时,您的主应用程序将继续正常运行。您当然希望在线程和主应用程序之间添加通信,以便您可以更新进度条或类似的反馈机制。

如果您不想处理多个线程,另一种方法是创建一个包含用于复制操作的状态变量的类和一个从主应用程序定期调用的成员函数,以便在每次调用时复制一定数量的字节.

于 2009-05-10T01:24:10.870 回答
1

你这里有两个问题。首先是 GUI 线程在复制大文件时没有响应。正如其他人所建议的那样,您应该使用后台线程来解决这个问题。

另一个问题是您当前的文件复制例程不支持进度回调函数。以下问题的公认答案包含您编写自己的解决方案所需的信息:

我可以在 .NET 中使用 FileInfo.CopyTo() 显示文件复制进度吗?

编辑:我刚刚为 CopyFileEx 找到了这个包装类。我测试了它,效果很好!

using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;

namespace FileCopyTest {
    public sealed class FileRoutines {
        public static void CopyFile(FileInfo source, FileInfo destination) {
            CopyFile(source, destination, CopyFileOptions.None);
        }

        public static void CopyFile(FileInfo source, FileInfo destination,
            CopyFileOptions options) {
            CopyFile(source, destination, options, null);
        }

        public static void CopyFile(FileInfo source, FileInfo destination,
            CopyFileOptions options, CopyFileCallback callback) {
            CopyFile(source, destination, options, callback, null);
        }

        public static void CopyFile(FileInfo source, FileInfo destination,
            CopyFileOptions options, CopyFileCallback callback, object state) {
            if (source == null) throw new ArgumentNullException("source");
            if (destination == null)
                throw new ArgumentNullException("destination");
            if ((options & ~CopyFileOptions.All) != 0)
                throw new ArgumentOutOfRangeException("options");

            new FileIOPermission(
                FileIOPermissionAccess.Read, source.FullName).Demand();
            new FileIOPermission(
                FileIOPermissionAccess.Write, destination.FullName).Demand();

            CopyProgressRoutine cpr = callback == null ?
                null : new CopyProgressRoutine(new CopyProgressData(
                    source, destination, callback, state).CallbackHandler);

            bool cancel = false;
            if (!CopyFileEx(source.FullName, destination.FullName, cpr,
                IntPtr.Zero, ref cancel, (int)options)) {
                throw new IOException(new Win32Exception().Message);
            }
        }

        private class CopyProgressData {
            private FileInfo _source = null;
            private FileInfo _destination = null;
            private CopyFileCallback _callback = null;
            private object _state = null;

            public CopyProgressData(FileInfo source, FileInfo destination,
                CopyFileCallback callback, object state) {
                _source = source;
                _destination = destination;
                _callback = callback;
                _state = state;
            }

            public int CallbackHandler(
                long totalFileSize, long totalBytesTransferred,
                long streamSize, long streamBytesTransferred,
                int streamNumber, int callbackReason,
                IntPtr sourceFile, IntPtr destinationFile, IntPtr data) {
                return (int)_callback(_source, _destination, _state,
                    totalFileSize, totalBytesTransferred);
            }
        }

        private delegate int CopyProgressRoutine(
            long totalFileSize, long TotalBytesTransferred, long streamSize,
            long streamBytesTransferred, int streamNumber, int callbackReason,
            IntPtr sourceFile, IntPtr destinationFile, IntPtr data);

        [SuppressUnmanagedCodeSecurity]
        [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool CopyFileEx(
            string lpExistingFileName, string lpNewFileName,
            CopyProgressRoutine lpProgressRoutine,
            IntPtr lpData, ref bool pbCancel, int dwCopyFlags);
    }

    public delegate CopyFileCallbackAction CopyFileCallback(
        FileInfo source, FileInfo destination, object state,
        long totalFileSize, long totalBytesTransferred);

    public enum CopyFileCallbackAction {
        Continue = 0,
        Cancel = 1,
        Stop = 2,
        Quiet = 3
    }

    [Flags]
    public enum CopyFileOptions {
        None = 0x0,
        FailIfDestinationExists = 0x1,
        Restartable = 0x2,
        AllowDecryptedDestination = 0x8,
        All = FailIfDestinationExists | Restartable | AllowDecryptedDestination
    }
}
于 2009-05-11T00:30:06.360 回答
1

除了运行后台线程之外,您还应该注意,您一次一个字节地复制了 512M-2G 的数据。这将转化为对 ReadByte 和 WriteByte 的多达 20 亿次调用希望这些调用在某处缓冲,这样您就不会将 20亿次托管转换为非托管转换,但即便如此,这肯定会加起来。

内存不是免费的,但肯定很便宜。分配一个缓冲区(可能是 16K-64K)并分块复制。不,代码并不像您需要处理一种不读取整个块的情况那样简单,但我宁愿使用 2G/64K 方法调用而不是 2G。

于 2009-05-11T00:46:03.103 回答
0

您真正想要做的是拥有一个多线程应用程序并在后台线程中进行文件复制,这样您的主线程就不会被束缚。

于 2009-05-10T01:17:43.647 回答