Использование CountdownEvent и ManualResetEvent для управления потоками в ThreadPool
У меня есть следующий многопоточный отрывок кода, над которым я работал, чтобы сравнить файлы после заархивированной копии и распаковать. Приложение архивирует папку, содержащую переменное количество файлов различных размеров, копирует файлы на сервер и распаковывает их. Затем файлы сравниваются, и это сравнение направляется в ThreadPool
,
Вот текущий полный метод:
public void FolderMoverLogic(string folderPathToZip, string unzipOutputDir)
{
string folderRootDir = Path.GetDirectoryName(folderPathToZip);
string folderNameToZip = Path.GetFileName(folderPathToZip);
try
{
//Zips files in <folderPathToZip> into folder <zippedLocal>
TransferMethods.CreateZipExternal(folderPathToZip, zippedlocal);
//Copies zipped folder to server location
File.Copy(zippedlocal + "\\" + folderNameToZip + ".zip", zippedserver + "\\" + folderNameToZip + ".zip");
//Unzips files to final server directory
TransferMethods.UnZip(zippedserver + "\\" + folderNameToZip + ".zip", unzipOutputDir + "\\" + folderNameToZip, sizeof(Int32));
TransferMethods m = new TransferMethods();
//Enumerate Files for MD5 Hash Comparison
var files = from file in Directory.EnumerateFiles(folderPathToZip, "*", SearchOption.AllDirectories)
select new
{
File = file,
};
int fileCount = 0;
CountdownEvent countdown = new CountdownEvent(10000);
using (ManualResetEvent resetEvent = new ManualResetEvent(false))
{
foreach (var f in files)
{
Interlocked.Increment(ref fileCount);
countdown.Reset(fileCount);
try
{
ThreadPool.QueueUserWorkItem(
new WaitCallback(c =>
{
//Check if any of the hashes have been different and stop all threads for a reattempt
if (m.isFolderDifferent)
{
resetEvent.Set();
CancellationTokenSource cts = new CancellationTokenSource();
cts.Cancel(); // cancels the CancellationTokenSource
try
{
countdown.Wait(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("cde.Wait(preCanceledToken) threw OCE, as expected");
}
return;
}
else
{
//Sets m.isFolderDifferent to true if any files fail MD5 comparison
m.CompareFiles(f.File, folderRootDir, unzipOutputDir);
}
if (Interlocked.Decrement(ref fileCount) == 0)
{
resetEvent.Set();
}
countdown.Signal();
}));
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
countdown.Wait();
resetEvent.WaitOne();
resetEvent.Close();
}
}
catch (Exception Ex)
{
Console.WriteLine(Ex.Message);
}
}
Полезные ресурсы рассмотрены до сих пор:
Безопасно ли сигнализировать и немедленно закрыть ManualResetEvent?
Остановка всех потоков в.NET ThreadPool?
Требования к логике ThreadPool:
- Сравните все перечисленные файлы локально и на сервере
- Возврат из всех потоков, если хеширование не совпадает
Предыдущий код пула темы:
using (ManualResetEvent resetEvent = new ManualResetEvent(false))
{
foreach (var f in files)
{
testCount++;
try
{
//Thread t = new Thread(() => m.CompareFiles(f.File, unzipped, orglsource));
//t.Start();
//localThreads.Add(t);
ThreadPool.QueueUserWorkItem(
new WaitCallback(c =>
{
if (resetEvent.WaitOne(0)) //Here is the `ObjectDisposedException`
{
return;
}
if (!m.Folderdifferent)
{
m.CompareFiles(f.File, folderRootDir, unzipOutput);
}
else
{
resetEvent.Set();
}
if (Interlocked.Decrement(ref fileCountZipped) == 0)
{
resetEvent.Set();
}
}));
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
resetEvent.WaitOne();
}
Я получал ObjectDisposedExceptions
периодически с показанным предыдущим кодом.
Мои вопросы как таковые:
- Является ли текущий метод поточно-ориентированным?
- Звучит ли логика?
- Любые идеи по улучшению производительности или безопасности потоков
- Разрешает ли текущий метод, который у меня вверху, предыдущие исключения кода
Я тестировал этот код, и он работал без исключений, но я смотрю на некоторые более опытные отзывы.
1 ответ
Некоторые заметки:
- не должно было быть так?
CountdownEvent countdown = new CountdownEvent(files.Count());
- Это безопасно? - НЕТ - Мне просто не нравится идея с CountdownEvent, если ЛЮБАЯ операция с ЛЮБЫМ файлом завершается неудачно, вы не получаете сигнал и приложение зависает при обратном отсчете.Wait(), я предпочитаю вместо этого использовать Задачи TPL - и вместо
countdown.Wait()
и использоватьTask.WaitAll(tasks)
никогда не используйте прямую "переменную foreach" в потоках ( этот поток объясняет почему), поэтому вместо:
сделай это:foreach (var f in files) { Task.Run(() => { var whateveryDoWithIt = f.File; } }
foreach (var f in files) { var ftemp = f; Task.Run(() => { var whateveryDoWithIt = ftemp.File; } }
чтобы ответить, если это потокобезопасный, я бы ответил: да, если вы исправите вышеприведенные пункты, и все методы, используемые в нем, также являются потокобезопасными