Формы приложения «Не отвечает» при копировании больших файлов? - PullRequest
2 голосов
/ 10 мая 2009

У меня есть приложение для работы с файлами, над которым я работаю. Файлы регулярно от 500 МБ до 2 ГБ. Все отлично работает, но крайне раздражает, что приложение «перестает отвечать». То, что я хотел бы сделать, - это побайтное или мегапиксельное копирование с некоторыми 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 ]

6 голосов
/ 10 мая 2009

Вы должны использовать BackgroundWorker в вашей форме для копирования. Это позволит копировать файлы в отдельном потоке и позволит вашему интерфейсу реагировать. Есть некоторая сложность, но BackgroundWorker позаботится о многих деталях. Но есть множество примеров из того, что вы хотите сделать .

3 голосов
/ 10 мая 2009

Я бы хотел использовать функцию CopyFileEx. Если аналога этой функции не существует в библиотеке управляемых фреймворков, то Google в любом случае расскажет, как ее использовать: может быть, такая статья, как http://www.thetechscene.com/2008/09/copyfileex-with-progress-callback-in-c-using-pinvoke/

Причина, по которой я хочу использовать CopyFileEx, заключается в том, что я предполагаю, что он реализован в ядре O / S, при этом данные копируются из одного файла в другой в драйвере файловой системы без использования пользовательской памяти (не говоря уже о управляемой памяти) ).

3 голосов
/ 10 мая 2009

Вам необходимо использовать BackgroundWorkerThread для этого. Вот очень хороший пример того, как это сделать: Копирование файла с использованием фоновых рабочих потоков

1 голос
/ 11 мая 2009

В дополнение к фоновому потоку следует отметить, что вы копируете 512M-2G данных по одному байту за раз . Это переведет до 2 МИЛЛИАРДОВ вызовов в ReadByte и WriteByte. Надеюсь, эти вызовы буферизуются где-то, так что вы не сделаете 2 МИЛЛИАРДОВ управляемыми неуправляемыми переходами, но даже в этом случае это наверняка сложится.

Память не свободна, но она точно дешевая. Выделите буфер (возможно, 16K-64K) и скопируйте его кусками. Нет, код не так прост, как вам придется обрабатывать один случай не чтения всего блока, но я бы предпочел вызовы методов 2G / 64K более 2G.

1 голос
/ 11 мая 2009

У вас есть две проблемы здесь. Во-первых, поток GUI не реагирует при копировании больших файлов. Вы должны использовать фоновый поток, чтобы решить это, как предлагали другие.

Другая проблема заключается в том, что ваша текущая процедура копирования файлов не поддерживает функцию обратного вызова хода выполнения. Принятый ответ на вопрос ниже содержит информацию, необходимую для написания собственного решения:

Могу ли я показать прогресс копирования файла, используя FileInfo.CopyTo () в .NET?

EDIT: Я только что нашел этот класс-оболочку для 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
    }
}
1 голос
/ 10 мая 2009

Один из подходов заключается в выполнении операции копирования в отдельном потоке. Ваше основное приложение будет продолжать работать в обычном режиме, пока поток выполняет работу по копированию файла. Вы, конечно, захотите добавить связь между потоком и основным приложением, чтобы вы могли обновить индикатор выполнения или аналогичный механизм обратной связи.

Если вы не хотите иметь дело с несколькими потоками, другой подход заключается в создании класса, который содержит переменные состояния для операции копирования и функцию-член, которая периодически вызывается из вашего основного приложения, чтобы копировать определенное количество байтов каждый раз это называется.

1 голос
/ 10 мая 2009

Threading.ThreadPool.QueueUserWorkitem должен помочь вам в этом.

0 голосов
/ 10 мая 2009

Что вы действительно хотите сделать, так это создать многопоточное приложение и скопировать файл в фоновом потоке, чтобы ваш основной поток не был связан.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...