C # Файловый поток не блокируется до завершения операции записи / чтения - PullRequest
1 голос
/ 11 февраля 2011

Я пытаюсь написать класс, который будет копировать файл из одного места в другое и сообщать о прогрессе. Проблема, с которой я столкнулся, заключается в том, что при запуске приложения прогресс будет мгновенно сниматься с 0 до 100%, но файл все еще копируется в фоновом режиме.

    public void Copy(string sourceFile, string destinationFile)
    {
        _stopWatch.Start();

        _sourceStream = new FileStream(srcName, FileMode.Open);
        _destinationStream = new FileStream(destName, FileMode.CreateNew);

        read();
        //On a 500mb file, execution will reach here in about a second.
    }

    private void read()
    {
        int i = _sourceStream.Read(_buffer, 0, bufferSize);

        _completedBytes += i;

        if (i != 0)
        {
            _destinationStream.Write(_buffer, 0, i);

            TriggerProgressUpdate();

            read();
        }
    }

    private void TriggerProgressUpdate()
    {
        if (OnCopyProgress != null)
        {
            CopyProgressEventArgs arg = new CopyProgressEventArgs();
            arg.CompleteBytes = _completedBytes;

            if (_totalBytes == 0)
                _totalBytes = new FileInfo(srcName).Length;

            arg.TotalBytes = _totalBytes;

            OnCopyProgress(this, arg);
        }
    }

Похоже, что происходит, когда FileStream просто ставит в очередь операции в ОС, а не блокирует до завершения чтения или записи.

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

PS. Я использую тестовые переменные источника и назначения, поэтому они не соответствуют аргументам.

Спасибо Craig

Ответы [ 2 ]

6 голосов
/ 11 февраля 2011

Я не думаю, что это может поставить в очередь операции read ... в конце концов, у вас есть байтовый массив, в нем будут некоторые данные после вызова Read - эти данные лучше быть правильным. Вероятно, буферизуются только операции write .

Вы можете периодически вызывать Flush для выходного потока ... Я не знаю, как далеко зайдет Flush с точки зрения различных уровней кэширования, но вполне может подождать, пока данные на самом деле было написано. РЕДАКТИРОВАТЬ: Если вы знаете, что это FileStream, вы можете позвонить Flush(true), который будет ждать, пока данные действительно будут записаны на диск.

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

Я обеспокоен тем, что вы используете рекурсию здесь - для очень большого файла вы можете просто взорвать переполнение стека без веской причины. (CLR иногда может оптимизировать хвостовые рекурсивные методы, но не всегда). Я предлагаю вам использовать цикл вместо. Это также было бы более читабельным, ИМО:

public void Copy()
{
    int bytesRead;
    while ((bytesRead = _sourceStream.Read(_buffer, 0, _buffer.Length)) > 0)
    {
        _destinationStream.Write(_buffer, 0, bytesRead);
        _completedBytes += bytesRead;
        TriggerProgressUpdate();
        if (someAppropriateCondition)
        {
            _destinationStream.Flush();
        }
    }
}

Надеюсь, вы где-то избавляетесь от потоков, кстати. Лично я стараюсь избегать использования одноразовых переменных-членов, если это вообще возможно. Есть ли причина, по которой вы не можете просто использовать локальные переменные в операторе using?

2 голосов
/ 15 февраля 2011

После исследования я обнаружил, что использование «FileOptions.WriteThrough» в конструкторе FileStream отключит кэширование записи.Это заставляет мой прогресс сообщать правильно.Это, однако, требует снижения производительности, копирование занимает 13 секунд в Windows и 20 секунд в моем приложении.Я собираюсь попробовать оптимизировать код и отрегулировать размер буфера, чтобы посмотреть, смогу ли я немного ускорить процесс.

...