Tenho um processo que precisa mover intermitentemente alguns arquivos de um diretório para outro. Antes de mover os arquivos, preciso garantir que um usuário não esteja adicionando arquivos ao diretório no momento - se estiver, quero esperar até que nenhum arquivo novo tenha sido adicionado ao diretório por N segundos antes de continuar o processo (o valor de N realmente não importa aqui).
Minha ideia original era usar um FileSystemWatcher
para capturar eventos de criação de arquivo, usar o .Throttle()
método de Rx para obter o último evento gerado dentro do período de tempo especificado e usar o resultado disso para desabilitar um loop de atraso infinito. O problema é que esse método apenas esperará para sempre se um usuário nunca fizer nenhuma alteração no diretório. De alguma forma, preciso que isso espere pelo menos N segundos e, se não houver nenhuma alteração dentro desse período de tempo, continue. Se houver uma alteração dentro desse período de tempo, espere até que N segundos tenham se passado desde a última alteração.
Aqui está o que tenho até agora:
Console.WriteLine("start");
// wait to ensure no changes are being made to the directory
await Task.Run(async () =>
{
var watcher = new FileSystemWatcher("D:\\Users\\mwcjk1\\source\\repos\\RandomTestAppNet8\\RandomTestAppNet8\\");
watcher.EnableRaisingEvents = true;
watcher.Created += (o, a) => { Console.WriteLine("file changed!"); };
var subject = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
ev => watcher.Created += ev,
ev => watcher.Created -= ev
);
bool done = false;
// wait for 7 seconds after the last file creation
var disposable = subject.Throttle(TimeSpan.FromSeconds(7)).Subscribe(events => { done = true; });
Console.WriteLine("Set up observer");
while (!done)
{
await Task.Delay(1000);
//Console.WriteLine("Waited 1 sec");
}
disposable.Dispose();
});
// Continue with the rest of the method execution
Console.WriteLine("done!");
Eu também não gosto do Task.Delay()
loop - parece que deveria haver uma maneira muito mais elegante de fazer isso ( .Throttle()
parece tão perto de funcionar sozinho!), mas não consegui descobrir.
Qualquer conselho é muito apreciado, obrigado!
Primeiramente, para isso:
Um TaskCompletionSource é um adaptador de eventos/mundo reativo para
Task
mundo. Basicamente, ele expõe umTask
que pode ser concluído, com falha ou cancelado chamando métodos na fonte de conclusão.Você pode reescrever seu código com
Task.Delay
isto:Em segundo lugar, você também menciona
Isso ocorre porque você está esperando o evento disparar, mas ele não será disparado até que alterações sejam feitas no diretório.
O que eu sugeriria em vez disso é verificar se o diretório teve alguma alteração recentemente e, se tiver, esperar um pouco e tentar novamente. Por exemplo:
Agora a questão é como
HasAnyRecentChanges
será a implementação. Eu sugiro usar a coisa mais simples possível para começar - apenas verificar os últimos horários de modificação dos arquivos no diretório.Isso pode ser lento para um grande número de arquivos, portanto, usar
FileSystemWatcher
e manter o horário da última modificação em um campo pode ser uma otimização, especialmente se você executar isso com muita frequência ou para um pequeno N. No entanto, carregar os horários da última modificação de todos os arquivos ainda seria necessário para inicializar o campo na inicialização, de modo que essa implementação será necessária em ambos os casos.Desculpe pela falta de receita, acho que não conseguiria deixar o produto mais limpo com ela.
Uma abordagem possível é usar um timer que é reiniciado sempre que o usuário faz uma alteração. Algo como:
Este é um código de nível bem baixo, usando código de bloqueio em vez de algo assíncrono, mas ainda é bem direto. Se você precisar de código assíncrono, provavelmente poderá substituir o resetEvent por uma fonte TaskCompletion. Fazer a chamada FileSystemWatcher
OnFileSystemEvent
é deixado como um exercício.Você provavelmente quer fazer deste um objeto de longa duração, para que ele possa monitorar alterações mesmo quando você não estiver copiando nenhum arquivo. Como alternativa, verifique a data de modificação em qualquer arquivo na inicialização e inicialize o evento de redefinição como falso se qualquer arquivo tiver sido modificado dentro do tempo de atraso, bem como inicialize o timer com o tempo de vencimento restante.
Talvez você queira procurar o termo "debouncer". Aqui está uma implementação simples que eu não testei:
https://gist.github.com/lcnvdl/43bfdcb781d799df6b7e8e66fe3792db
Mas parece bom para mim.
Basta chamar Debounce() toda vez que algo mudar no seu diretório e passar seu método que copiará os arquivos.
Tente isto, input - é a fonte dos eventos do observador do seu sistema de arquivos:
Vale a pena mencionar que aqui está a implementação que fiz depois de receber sugestões de algumas outras respostas/comentários: