Отслеживание файлов FileSystemWatcher и отправка их по API
Я не совсем знаю как правильно описать бизнес-задачу, но она выглядит примерно следующим образом:
Добавляется в папку n файлов, мне их надо пачкой (т.е все 3 сразу) отправить по API. Не последовательно 3, а сразу 3
Если я реагирую на FileSystemWatcher.Created и внутри обработчика событий буду отправлять файлы - это будет некорректно. Есть необходимость отправлять документы "пачкой" (т.е в обработчике события метод АPI SendFiles(IEnumerable<string> files) который отправляет файлы контрагенту не вызвать, ибо иногда бывает необходимо отправлять не 1 файл, а 2 сразу (на сайте будет отображаться как пачка документов)). Но FileSystemWatcher реагирует на изменение файловой системы для каждого файла.. поэтому я даже ума не приложу как мне реализовать такой функционал.
Если не особо понятно что я имею в виду, то вот по пунктам:
- FileSystemWatcher может реагировать на n документов, но только последовательно. Т.е если будет 3 документа добавлено в папку - то будет 3 вызова события и три раза вызван метод API
- Отправлять документы в обработчике события не могу, потому что может быть потребность отправлять их не по одному, а сразу пачкой.
У меня сейчас примерно такие мысли по реализации:
- Отслеживаем добавление файла
- Добавляем в коллекцию
- Отправляем. Правда вот не знаю где мне вызывать метод отправки.
- Чистим коллекцию и ждем изменений снова.
static List<string> Files = new List<string>();
static void Main(string[] args)
{
var watcher = new FileSystemWatcher(path2);
watcher.Created += OnChanged;
watcher.EnableRaisingEvents = true;
Console.ReadLine();
}
private static void OnChanged(object sender, FileSystemEventArgs e)
{
Files.Add(e.FullName);
}
private static void Send(IEnumerable<string> files)
{
//Тут отправка по API пакета документов. Не через цикл.
}
Ответы (2 шт):
Вас спасет шаблон проектирования Producer/Consumer.
Самая простая реализация Producer/Consumer - это коллекция BlockingCollection, она умеет блокировать поток, пока не в нее что-то не добавишь. Ожидание новых объектов в этой коллекции синхронное, поэтму обработку надо запускать в отдельном потоке.
Пример:
static void ConsumerLoop(BlockingCollection<string> collection, int delay)
{
while (true)
{
List<string> items = new List<string>();
try
{
do
{
do
{
items.Add(collection.Take());
}
while (collection.Count > 0);
Thread.Sleep(delay);
}
while (collection.Count > 0);
}
catch (InvalidOperationException)
{
break;
}
finally
{
if (items.Count > 0)
Console.WriteLine("output: " + string.Join(" ", items));
}
}
}
В блоке finally как раз можно вызывать отправку документов.
Пример использования:
static async Task Main(string[] args)
{
BlockingCollection<string> collection = new BlockingCollection<string>();
Task task = Task.Run(() => ConsumerLoop(collection, 1000));
while (true)
{
string input = Console.ReadLine();
if (input.Length == 0)
break;
collection.Add(input);
}
collection.CompleteAdding();
await task;
}
collection.Add(input); - стоит как раз вызывать когда случилось событие добавления элемента. 1000 - сколько ждать в миллисекундах после добавления последнего элемента. То есть если элементы добавляются с периодом меньше секунды - они группируются вместе. Если прошло больше секунды, происходит обработка группы и переход к ожиданию элементов следующей группы.
Пример вывода в консоль
1
2
3
4
output: 1 2 3 4
5
5
5
5
5
5
5
output: 5 5 5 5 5 5 5
1
1
output: 1 1
Если хочется асинхронности без вызова потока, посмотрите на модные System.Threading.Channels вместо BlockingCollection, работать с ними немного сложнее, но в плане асинхронной работы с данными они лучше.
Кстати, по поводу вашего прошлого вопроса по отслеживанию дубликатных событий. В этой реализации оно элементарно решается заменой List на HashSet. То есть дубликаты будут исключаться на этапе группировки автоматически.
using System;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
class Program
{
static FileSystemWatcher watcher;
static int delay;
static string path;
static string zipPath;
static void Main()
{
delay = 1000;
path = "...";
zipPath = "..."; // Path.GetTempFileName();
watcher = new FileSystemWatcher(path);
watcher.Created += OnChanged;
watcher.EnableRaisingEvents = true;
Console.ReadLine();
}
private static async void OnChanged(object sender, FileSystemEventArgs e)
{
watcher.EnableRaisingEvents = false;
await Task.Delay(delay);
ZipFile.CreateFromDirectory(path, zipPath);
// Send zipPath
File.Delete(zipPath);
watcher.EnableRaisingEvents = true;
}
}
При добавлении первого файла в отслеживаемую папку срабатывает событие.
В нём отключаем слежение за событиями.
Ждём некоторое время (delay).
Потом файлы из папки преобразуем в архив.
Архив посылаем клиенту.
После чего удаляем архив.
И снова включаем отслеживание событий.
Это лишь набросок кода.