A execução de múltiplas threads, iniciando novas uma como a outra termina
-
25-09-2019 - |
Pergunta
Eu tenho um aplicativo que tem muitos casos.Cada caso tem muitos multipage arquivos tif.Eu preciso converter o tf arquivos para o arquivo pdf.Desde que há assim muitos arquivos, eu pensei que eu poderia thread o processo de conversão.Atualmente estou limitando o processo de dez conversões por um tempo (eu.e dez lagartas).Quando a conversão for concluída, o outro deve começar.
Esta é a configuração atual que estou usando.
private void ConvertFiles()
{
List<AutoResetEvent> semaphores = new List<AutoResetEvet>();
foreach(String fileName in filesToConvert)
{
String file = fileName;
if(semaphores.Count >= 10)
{
WaitHandle.WaitAny(semaphores.ToArray());
}
AutoResetEvent semaphore = new AutoResetEvent(false);
semaphores.Add(semaphore);
ThreadPool.QueueUserWorkItem(
delegate
{
Convert(file);
semaphore.Set();
semaphores.Remove(semaphore);
}, null);
}
if(semaphores.Count > 0)
{
WaitHandle.WaitAll(semaphores.ToArray());
}
}
Com isso, às vezes resulta em uma exceção informando que o WaitHandle.WaitAll() ou WaitHandle.WaitAny() matriz de parâmetros não devem ter um comprimento superior a 65 anos.O que estou fazendo de errado nesta abordagem e como posso corrigi-lo?
Solução
Existem alguns problemas com o que você escreveu.
1º, não é thread-safe.Você tem vários threads de adicionar, remover e esperando a matriz de AutoResetEvents.Os elementos individuais da Lista pode ser acessado em segmentos separados, mas nada que adiciona, remove ou verificações de todos os elementos (como o WaitAny chamada), precisam fazê-lo dentro de um bloqueio.
2º, não há nenhuma garantia de que seu código vai processar apenas 10 arquivos de uma vez.O código entre o momento em que o tamanho da Lista é verificada, e o ponto em que um novo item é adicionado está aberto para vários segmentos para passar.
3º, há potencial para as threads que começou no QueueUserWorkItem para converter o mesmo arquivo.Sem capturar o nome do arquivo dentro do loop, a thread que converte o arquivo que irá utilizar o valor que está no nome de arquivo quando ele é executado, o que NÃO estava no nome de arquivo quando você chamou QueueUserWorkItem.
Este codeproject artigo deve apontar na direção certa para o que você está tentando fazer: http://www.codeproject.com/KB/threads/SchedulingEngine.aspx
EDITAR:
var semaphores = new List<AutoResetEvent>();
foreach (String fileName in filesToConvert)
{
String file = fileName;
AutoResetEvent[] array;
lock (semaphores)
{
array = semaphores.ToArray();
}
if (array.Count() >= 10)
{
WaitHandle.WaitAny(array);
}
var semaphore = new AutoResetEvent(false);
lock (semaphores)
{
semaphores.Add(semaphore);
}
ThreadPool.QueueUserWorkItem(
delegate
{
Convert(file);
lock (semaphores)
{
semaphores.Remove(semaphore);
}
semaphore.Set();
}, null);
}
Pessoalmente, eu não acho que eu iria fazê-lo desta forma...mas, trabalhando com o código que você tem, isso deve funcionar.
Outras dicas
Você está usando um semáforo real (System.Threading)? Ao usar semáforos, você normalmente aloca seus recursos máximos e ele bloqueia automaticamente (conforme você adiciona e libera). Você pode seguir a abordagem de espera, mas estou sentindo que você escolheu o caminho mais difícil.
Parece que você precisa remover a alça do desencadeado, a função de espera para prosseguir
if(semaphores.Count >= 10)
{
int index = WaitHandle.WaitAny(semaphores.ToArray());
semaphores.RemoveAt(index);
}
Então, basicamente, eu removeria o:
semaphores.Remove(semaphore);
Ligue para o thread e use o acima para remover o evento sinalizado e ver se isso funciona.
Talvez você não deva criar tantos eventos?
// input
var filesToConvert = new List<string>();
Action<string> Convert = Console.WriteLine;
// limit
const int MaxThreadsCount = 10;
var fileConverted = new AutoResetEvent(false);
long threadsCount = 0;
// start
foreach (var file in filesToConvert) {
if (threadsCount++ > MaxThreadsCount) // reached max threads count
fileConverted.WaitOne(); // wait for one of started threads
Interlocked.Increment(ref threadsCount);
ThreadPool.QueueUserWorkItem(
delegate {
Convert(file);
Interlocked.Decrement(ref threadsCount);
fileConverted.Set();
});
}
// wait
while (Interlocked.Read(ref threadsCount) > 0) // paranoia?
fileConverted.WaitOne();