Асинхронное копирование файлов c
Возможно ли скопировать файл в несколько потоков? Если да, то как? Я пытаюсь сделать что-то подобное, но оно ожидаемо не работает:
В таком виде, конечно, не работает. Даже поток не запускается (говорит, нет доступа к закрытому потоку). Что я понимаю не верно?
Вы сами диспозите FileStream'ы при помощи using блока. Поэтому когда начинает выполнятся код из потока, они уже закрыты.
2 ответа 2
Возможно ли скопировать файл в несколько потоков? Если да, то как?
Возможно. Например, именно так и делает Utorrent. Он сначала резервирует область памяти на винчестере а потом в куче потоков скачивает его из интернета когда это становится возможным (когда на раздаче кто-то появляется) и потом на определенную область зарезервированной памяти вносит изменения в виде бинарного кода с этой же области у оригинального файла из интернета.
А теперь перейдем к НЕЗАДАННОМУ вопросу: Имеет ли это смысл?
Распараллеливание дает прирост скорости исключительно на работе с CPU/GPU. Т.к. поток имеет свои ограничения, но незадействованного ресурса у CPU/GPU еще достаточно.
Но копирование, это работа с HDD. Работа с HDD не станет быстрее если копировать файл в несколько потоков. Она станет МЕДЛЕННЕЕ.
Да потому, что HDD работает на основе физики и ты упрешься в возможности механики винчестера. Там есть читающая головка и есть "блин" который крутится с некоей скоростью. Именно по этому у HDD самая высокая скорость записи или чтения -- последовательная. То есть если на блине записаны данные последовательно -- чтение будет быстрым(относительно рандомно размещенных данных). Если нет -- головку нужно перемещать из одной позиции на другую. Распараллеливание задачи копирования сделает так, что головку нужно будет перемещать чаще(логично, не так ли?).
В случае с SSD ты не получишь замедления копирования. Но так же и прироста не получишь. Там нету механики, а это дает высокую скорость чтения/записи рандомно размещенных данных. Зато все равно есть ограничение по скорости. То есть точно так же. Ты хоть последовательно копируй данные, хоть параллельно -- ты получишь идентичную скорость.
А что если копировать через сеть? Например через Ethernet-кабель, получим ли мы желаемый профит? Снова таки нет. Мы упремся в ограничения скорости кабеля. Как упирались в скорость SSD.
А теперь когда мы выяснили что это возможно, но бессмысленно.
Стоит ли решать данную задачу тобой или кем-либо в принципе?)
Метод для обработки данных (что там тебе нужно с ними сделать я не знаю, но ты писал что это не просто копирование). Стоит учесть что я не знаю изменяется ли количество данных или нет, если изменяется хотя бы на один байт -- это стоит учитывать при записи в конечный файл.
Вызываешь нужное количество потоков с вызовом этих методов. Можно попробовать как через таски так и через трэды.
Для доступа к файлам можно использовать функцию Async. При использовании функции Async вы можете вызывать асинхронные методы без использования обратных вызовов или разделения вашего кода на множество методов или лямбда-выражений. Для выполнения последовательного кода асинхронно просто вызовите асинхронный метод вместо синхронного метода и добавьте несколько ключевых слов в код.
Можно рассмотреть следующие причины для добавления асинхронности для вызовов для доступа к файлам.
Чтение текста
Следующий пример считывает текст из файла.
Пример конечного элемента управления
Для каждого файла метод WriteAsync возвращает задачу, которая затем добавляется в список задач. Оператор await Task.WhenAll(tasks); существует и возобновляется в методе, как только завершается обработка файла для всех задач.
Пример закрывает все экземпляры FileStream в блоке finally после завершения всех задач. Если бы вместо этого каждый FileStream был бы создан в операторе using , то FileStream можно было бы удалить до завершения задачи.
Любое увеличение производительности зависит почти полностью от параллельной, а не асинхронной обработки. Преимущества асинхронности в том, что она не привязана к количеству потоков и не связана с потоком пользовательского интерфейса.
При использовании методов WriteAsync и ReadAsync можно указать CancellationToken, который позволяет отменить операцию в середине потока. Подробные сведения см. в статье Отмена в управляемых потоках.
Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
Асинхронно считывает байты из текущего потока и записывает их в другой поток. Обе позиции потоков перемещаются по количеству скопированных байтов.
Применяется к
CopyToAsync(Stream, Int32, CancellationToken)
Асинхронно считывает байты из текущего потока и записывает их в другой поток, используя указанный размер буфера и токен отмены. Обе позиции потоков перемещаются по количеству скопированных байтов.
Параметры
Поток, в который будет скопировано содержимое текущего потока.
Размер (в байтах) буфера. Это значение должно быть больше нуля. Размер по умолчанию — 81920.
Токен для отслеживания запросов отмены. Значение по умолчанию — None.
Возвращаемое значение
Задача, представляющая асинхронную операцию копирования.
Исключения
destination имеет значение null .
Параметр buffersize имеет отрицательное значение или равен нулю.
Текущий поток или поток назначения удаляется.
Текущий поток не поддерживает чтение или поток назначения не поддерживает запись.
Простой пример
Комментарии
Если операция отменена до ее завершения, возвращаемая задача содержит Canceled значение свойства Status .
Копирование начинается с текущей позиции в текущем потоке.
Пример копирования между двумя потоками см. в разделе перегрузки CopyToAsync(Stream) .
Простой пример
CopyToAsync(Stream)
Асинхронно считывает байты из текущего потока и записывает их в другой поток. Обе позиции потоков перемещаются по количеству скопированных байтов.
Параметры
Поток, в который будет скопировано содержимое текущего потока.
Возвращаемое значение
Задача, представляющая асинхронную операцию копирования.
Исключения
destination имеет значение null .
Текущий поток или поток назначения удаляется.
Текущий поток не поддерживает чтение или поток назначения не поддерживает запись.
Применяется к
CopyToAsync(Stream, CancellationToken)
Асинхронно считывает байты из текущего потока и записывает их в другой поток, используя указанный токен отмены. Обе позиции потоков перемещаются по количеству скопированных байтов.
Параметры
Поток, в который будет скопировано содержимое текущего потока.
Токен для отслеживания запросов отмены. Значение по умолчанию — None.
Возвращаемое значение
Задача, представляющая асинхронную операцию копирования.
Пример конечного элемента управления
Первоначальная строка с оператором await sourceStream.WriteAsync(encodedText, 0, encodedText.Length); является сокращенной формой записи двух следующих операторов:
Первый оператор возвращает задачу и вызывает запуск обработки файла. Вторая строка с await немедленно оставляет метод и возвращается в другую задачу. При окончании обработки файла выполнение возвращается в точку выполнения, которая следует за await.
Параллельный асинхронный ввод-вывод
В следующем примере показана параллельная обработка при записи 10 текстовых файлов.
Примеры
В следующем примере показано, как использовать два FileStream объекта для асинхронного копирования файлов из одного каталога в другой. Класс FileStream является производным от класса Stream . Обратите внимание, что Click обработчик событий для Button элемента управления помечен модификатором async , так как он вызывает асинхронный метод.
Использование соответствующих классов
В простых примерах в этом разделе демонстрируются File.WriteAllTextAsync и File.ReadAllTextAsync. Для конечного контроля над операциями файлового ввода-вывода используйте класс FileStream, содержащий параметр, который вызывает асинхронный ввод-вывод на уровне операционной системы. С помощью этого параметра можно избежать блокирования пула потоков во многих случаях. Чтобы включить этот параметр, необходимо добавить в вызов конструктора аргумент useAsync=true или options=FileOptions.Asynchronous .
Этот параметр нельзя использовать с классами StreamReader и StreamWriter, если вы открываете их напрямую (указав путь к файлу). При этом параметр можно использовать, если им предоставлен Stream, открытый классом FileStream. Асинхронные вызовы выполняются быстрее в приложениях пользовательского интерфейса, даже если поток в пуле потоков блокирован, поскольку поток пользовательского интерфейса не блокирован во время ожидания.
Применяется к
Перегрузки
Асинхронно считывает байты из текущего потока и записывает их в другой поток, используя указанный размер буфера и токен отмены. Обе позиции потоков перемещаются по количеству скопированных байтов.
Асинхронно считывает байты из текущего потока и записывает их в другой поток, используя указанный токен отмены. Обе позиции потоков перемещаются по количеству скопированных байтов.
Асинхронно считывает байты из текущего потока и записывает их в другой поток, используя указанный размер буфера. Обе позиции потоков перемещаются по количеству скопированных байтов.
Асинхронно считывает байты из текущего потока и записывает их в другой поток. Обе позиции потоков перемещаются по количеству скопированных байтов.
Простой пример
Запись текста
Следующие примеры записывают текст в файл. На каждой точке await происходит немедленный выход из метода. После завершения файлового ввода-вывода метод возобновляет работу с пункта, следующего за await. Модификатор async в определении методов требует наличия await в теле метода.
CopyToAsync(Stream, Int32)
Асинхронно считывает байты из текущего потока и записывает их в другой поток, используя указанный размер буфера. Обе позиции потоков перемещаются по количеству скопированных байтов.
Параметры
Поток, в который будет скопировано содержимое текущего потока.
Размер (в байтах) буфера. Это значение должно быть больше нуля. Размер по умолчанию — 81920.
Возвращаемое значение
Задача, представляющая асинхронную операцию копирования.
Исключения
destination имеет значение null .
Параметр buffersize имеет отрицательное значение или равен нулю.
Текущий поток или поток назначения удаляется.
Текущий поток не поддерживает чтение или поток назначения не поддерживает запись.
Пример конечного элемента управления
Комментарии
Либо копирование файлов в нескольких потоках, либо копирование в одном потоке, но отдельном от основного (GUI) потока.
В WinAPI асинхронное копирование файлов — это использование так называемого «асинхронного ввода-вывода» (Overlapped I/O), механизма, который не поднимает новых потоков (по крайней мере явно), но позволяет параллельно с вводом-выводом делать что-то другое. В данном случае — читать и записывать одновременно.
Что это даёт?
1. Уменьшает простои программы и этим ускоряет копирование.
2. Если копирование в пределах одного механического AHCI-диска (из каталога в каталог, с раздела на раздел)— это позволяет оптимизировать маршруты головки на уровне драйвера или контроллера.
3. Если копирование между разными накопителями — накопители будут действовать параллельно.
У тебя 100 файлов. Каждый копируется по 5 секунд.
Если ты будешь копировать их по очереди - это синхронное копирование файлов, которое займет не менее 500 секунд.
Асинхронное копирование - это копирование нескольких файлов за раз. Например, группами по 5 файлов. Каждая группа будет копировать по 10 секунд, а все вместе, следовательно, 100/5*10 = 200 секунд.
Асинхронное копирование не всегда дает выигрыш. Но чаще всего копирование нескольких файлов за раз эффективнее.
Есть и еще бонус. Если есть один огромный файл и много маленьких. Если ты начнешь копировать большой файл, то не дождешься маленьких файлов еще очень долго.
Но если копировать асинхронно всего в 2 поток, то параллельно первому огромному файлу можно успеть скопировать все маленькие и уже что-то начинать с ними делать на том конце, не дожидаясь первого огромного файла.
Идея асинхронности в том и заключается, что некое действие разбивается на маленькие этапы и эти этапы запускаются одновременно. А там - кто первый дойдет к финишу, тот быстрее освободит ресурсы для следующего в очереди.
Часть моего проекта отвечает за копирование каталогов из одного места в другое. Сначала рекурсивно получаю список файлов в папке (отбрасывая файлы с исключениями), затем с этим списком работаю. Проблема состоит в производительности. Копировать файлы по одному неэффективно. Однако, если для каждого выделить поток (или Task), это тоже плохое решение, потому что:
- Количество и размер файлов могут быть разными.
- Если чтение/запись происходит в пределах одного ЖЕСТКОГО диска, то при слишком частом обращении в разные каталоги он будет физически не успевать и производительность уменьшится.
- Если при копировании одного из файлов возникает исключение, то программа жестко зависает на несколько секунд (проверял)
Нужен особый алгоритм, который будет совмещать синхронность и асинхронность. Например, находить какую-то зависимость между количеством и размером файлов, делить их на группы и др.
Наставьте меня на правильное решение, пожалуйста.
Копировать в несколько потоков - плохая затея, и название этой затеи - фрагментация скопированных файлов. Для SSD это особо пофигу, но вот для HDD фрагментация это довольно плохо(да и собственно сама процедура доступа в несколько потоков)
@ГеннадийП - про фрагментацию, в целом, дельное замечание. Но ведь можно сразу выделить место на диске для всего файла, ещё до копирования.
Очень полезная информация про фрагментацию, спасибо! А что, если разбивать копирование на шаги? Это скажется на фрагментации диска и скорости, например, если на каждом шагу будет копироваться по 3-5 файлов легких файлов параллельно, а тяжелые в конце по очереди? И стоит ли заморачиваться, чтобы узнать тип хранилища (SSD, HDD) и в зависимости от этого по-разному действовать?
@AlexanderPetrov Не спорю, можно выделить. Но у HDD очень снижается скорость работы если читать/записывать в несколько потоков, а максимальная скорость достигается при линейном доступе. Оно и понятно: лишние перемещения головок сильно тормозят работу.
Комментарии
Копирование начинается с текущей позиции в текущем потоке.
Пример копирования между двумя потоками см. в разделе перегрузки CopyToAsync(Stream) .
Комментарии
Если операция отменена до ее завершения, возвращаемая задача содержит Canceled значение свойства Status .
Копирование начинается с текущей позиции в текущем потоке.
Пример копирования между двумя потоками см. в разделе перегрузки CopyToAsync(Stream) .
Читайте также: