Использование 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?

MSDN CountdownEvent

Требования к логике 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. Является ли текущий метод поточно-ориентированным?
  2. Звучит ли логика?
  3. Любые идеи по улучшению производительности или безопасности потоков
  4. Разрешает ли текущий метод, который у меня вверху, предыдущие исключения кода

Я тестировал этот код, и он работал без исключений, но я смотрю на некоторые более опытные отзывы.

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; 
        }
    }

  • чтобы ответить, если это потокобезопасный, я бы ответил: да, если вы исправите вышеприведенные пункты, и все методы, используемые в нем, также являются потокобезопасными

Другие вопросы по тегам