Подождите, пока папка и ее содержимое будут полностью скопированы - PullRequest
0 голосов
/ 06 ноября 2018

Я использую FileSystemWatcher для отслеживания папки и проверки, появляется ли новая папка. Затем мне нужно скопировать некоторые файлы оттуда куда-нибудь еще. Но я должен сначала ждать, пока папка будет скопирована Это код.

bool waiting = true;
var watcher = new FileSystemWatcher(path);
watcher.Created += (obj, args) =>
{
    //do something
    waiting = false;        
};
watcher.NotifyFilter = NotifyFilters.DirectoryName;
watcher.EnableRaisingEvents = true;

while(waiting)
{

}

Проблема в том, что как только папка создается, я получаю уведомление, и происходит часть "сделать что-то", даже если папка еще не полностью скопирована, и, очевидно, у меня возникают проблемы. Мне нужно как-то подождать, пока папка полностью скопируется, прежде чем делать что-то. Как я могу это сделать?

1 Ответ

0 голосов
/ 06 ноября 2018

Это общая проблема, с которой сталкиваются все приложения для синхронизации файлов, такие как Dropbox, OneDrive и т. Д. Копирование большого файла включает одно событие создания и несколько Changed событий, поскольку файл не может быть создан в одном операция. Событие Closed отсутствует, поэтому приложения могут только дождаться остановки событий changed, прежде чем они снова начнут хэшироваться и синхронизироваться.

Фактически, вы заметите, что когда вы копируете много файлов в папку, отслеживаемую Dropbox и др., Они останавливают то, что делали, и немного ждут после того, как копирование прекращается.

Реактивные расширения в .NET, Java, Javascript и других языках позволяют использовать LINQ-подобные запросы к потокам событий. Один из доступных операторов - Debounce , который ожидает, пока поток событий не успокоится, прежде чем испустить последний. Этот оператор (называемый Throttle в .NET) может быть использован для обнаружения прекращения создания файлов.

Этот пример ждет 5 секунд после последнего создания файла, прежде чем вызывать метод подписчика:

using (var fsw = new FileSystemWatcher(@"K:\Backups"))
{
    fsw.InternalBufferSize = 65536;
    var creations = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
        h => fsw.Created += h,
        h => fsw.Created -= h);

    creations.Timestamp()
        .Throttle(TimeSpan.FromSeconds(5))
        .Select(x => $"{ x.Timestamp} : {DateTime.Now - x.Timestamp} -  {x.Value.EventArgs.FullPath}")
        .Subscribe(Console.WriteLine);
}

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

Возвращая только последнее событие, этот единственный Throttle() может использоваться для обработки сигналов всей папки. Для обработки отдельных файлов нам нужно регулировать потоки событий, генерируемые каждым файлом отдельно. Другими словами, чтобы сгруппировать события по файлу:

var obs = from creation in creations
            group creation by creation.EventArgs.FullPath into g
            from last in g.Throttle(TimeSpan.FromSeconds(5))
            select last.EventArgs.FullPath;
obs.Subscribe(Console.WriteLine);

Синтаксис запроса LINQ в этом случае намного проще. group by группирует события по имени файла, а затем Throttle() генерирует последнее событие для файла через 5 секунд тишины.

Чтобы эта работа работала с большими файлами, нам нужно объединить события «Создано» и «Изменено». Это работа оператора слияния:

var changes = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
        h => fsw.Changed += h,
        h => fsw.Changed -= h)

var obs = from evt in creations.Merge(changes)
        group evt by evt.EventArgs.FullPath into g
        from last in g.Throttle(TimeSpan.FromSeconds(5))
        select $"{last.EventArgs.ChangeType} - {last.EventArgs.FullPath}";

И вот куда все идет БУМ!

Копирование в Windows 10 вызывает только два Изменить события, последнее только после завершения копирования. Если файл слишком велик (ГБ или 100 МБ, в зависимости от скорости диска), второе событие может занять слишком много времени.

Одним из вариантов будет установка большого промежутка времени, достаточно большого, чтобы охватить большинство операций ввода-вывода, например TimeSpan.FromMinutes(1).

Другой вариант - использовать другой оператор, Buffer (), который может захватывать указанное количество элементов в пакете и возвращать их в виде массива:

var obs = from evt in creations.Merge(changes)
        group evt by evt.EventArgs.FullPath into g
        from last in g.Buffer(3)
        select $"{last[2].EventArgs.ChangeType} - {last[2].EventArgs.FullPath}";

Это работает только при копировании. Сохранение файла, например, из Excel или Word, может привести к нескольким событиям Changed, так как приложение вносит в файл несколько изменений.

Буфер также может принимать аргумент Timespan, который можно использовать для сбора всех Измененных событий для каждого файла и проверки их на соответствие одному из двух шаблонов. Несколько изменений? Просто начало / конец Измененное событие? Когда произошло последнее событие (это предоставлено Timestamp)?

...