【问题标题】:Forms application "Not Responding" when copying large files?复制大文件时表单应用程序“无响应”?
【发布时间】:2010-10-25 01:47:46
【问题描述】:

我正在开发一个文件管理器应用程序。这些文件通常为 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 调用或其他方法的示例。我必须在这里遗漏一些东西,所以如果有人能让我在这里走上正确的道路,我将非常感激。

提前致谢!

【问题讨论】:

  • 我想我真正想要的是能够在复制文件时看到进度。我可以添加线程,但我仍然不会在屏幕上实时收到任何与副本有关的反馈。也许我在这一点上并不清楚。感谢您的快速回复!
  • 如何在单独的线程中进行复制操作,然后让主应用程序轮询正在处理的文件的文件大小并使用它来更新进度。想法?
  • 我认为工作线程可以更新进度条,如果它小心的话(使用BeginInvoke)。

标签: c# winforms file progress-bar copying


【解决方案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
    }
}

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

          【解决方案5】:

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

          【讨论】:

            【解决方案6】:

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

            【讨论】:

              【解决方案7】:

              您应该在表单上使用BackgroundWorker 进行复制。它将允许在单独的线程上完成文件复制,并让您的 UI 做出响应。有一些额外的复杂性,但 BackgroundWorker 会为您处理很多工作。但是,exampleswhat you want to do 有很多。

              【讨论】:

                【解决方案8】:

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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-08-18
                  • 2018-04-14
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多