复制大文件时表单应用程序“无响应”?

时间:2009-05-10 01:14:31

标签: c# winforms file progress-bar copying

我有一个我正在处理的文件管理器应用程序。这些文件通常为500MB到2GB。一切正常,但应用程序“停止响应”非常烦人。我想做的是逐字节或兆字节复制,每次读/写操作后都有一些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调用或其他方式的例子。我必须在这里遗漏一些东西,所以如果有人能让我走在正确的轨道上,我真的很感激。

提前致谢!

8 个答案:

答案 0 :(得分:6)

您应该在表单上使用BackgroundWorker进行复制。它将允许文件副本在单独的线程上完成,并让您的UI响应。虽然增加了一些复杂性,但BackgroundWorker会为您处理大量的管道工作。但是,examples有很多what you want to do

答案 1 :(得分:3)

您需要使用 BackgroundWorkerThread 来完成此操作。以下是如何做到这一点的一个很好的例子:Copy File Using Background Worker Threads

答案 2 :(得分:3)

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

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

答案 3 :(得分:1)

Threading.ThreadPool.QueueUserWorkitem可以让您顺利上路。

答案 4 :(得分:1)

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

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

答案 5 :(得分:1)

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

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

Can I show file copy progress using FileInfo.CopyTo() in .NET?

编辑: 我刚发现这个wrapper class for 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
    }
}

答案 6 :(得分:1)

除了运行后台线程之外,您还应注意,您一次只复制512M-2G数据一个字节。这将转换为最多2次 BILLION 调用ReadByte和WriteByte。希望这些调用缓冲在某个地方,这样你就不会使2 BILLION 成为非托管转换,但即便这样也肯定会加起来。

记忆不是免费的,但确实很便宜。分配缓冲区(可能是16K-64K)并以块的形式复制。不,代码不是那么简单,你需要处理一个不读取整个块的情况,但我宁愿在2G上进行2G / 64K方法调用。

答案 7 :(得分:0)

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