【问题标题】:Using CountdownEvent and ManualResetEvent to control threads in ThreadPool使用 CountdownEvent 和 ManualResetEvent 控制 ThreadPool 中的线程
【发布时间】:2013-03-17 07:55:56
【问题描述】:

我有以下多线程代码摘录,我一直在研究这些摘录来比较压缩副本和解压缩后的文件。 该应用程序正在压缩一个文件夹,其中包含可变数量的各种大小的文件,将文件复制到服务器并解压缩它们。然后对文件进行比较,并将比较结果输出到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);
    }
}

目前看到的有用资源:

Is it safe to signal and immediately close a ManualResetEvent?

Stopping all thread in .NET ThreadPool?

MSDN CountdownEvent

线程池逻辑要求:

  • 比较本地和服务器上的所有枚举文件
  • 如果散列不匹配则从所有线程返回

以前的线程池代码

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. 我在顶部的当前方法是否解决了以前的代码异常

我一直在测试这段代码,它一直在正常工作,但我正在查看一些更有经验的反馈。

【问题讨论】:

  • 你问错问题了。当您使用线程时,您还没有检查您的代码是否真的更快。它不是。因此,尝试使这种危险且麻烦的代码工作是没有意义的。
  • @HansPassant 如果我正在使用的文件大小 > 1 GB,线程会有所帮助吗?
  • 你不应该问我。你应该测量。没有。

标签: c# multithreading threadpool manualresetevent countdownevent


【解决方案1】:

一些注意事项:

  • 不应该是这样的吗?:
    CountdownEvent countdown = new CountdownEvent(files.Count()); 
  • 安全吗? - NO - 我只是不喜欢使用 CountdownEvent 的想法,如果对任何文件的任何操作失败,您都不会收到信号并且应用程序在 countdown.Wait() 上挂起,我更喜欢使用 TPL Tasks代替 - 而不是 countdown.Wait() 并使用 Task.WaitAll(tasks)
  • 永远不要在线程中直接使用“foreach 变量”(this thread explains why),所以不要使用:

    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; 
        }
    }
  • 回答它是否是线程安全的

【讨论】:

  • 是的,应该是 files.Count() 让我测试一下 Tasks 库,看看我得到了什么。
  • 我将来会使用 Tasks 库。我不知道它具有它的所有功能。同样关于上面@HansPassant 的评论,我对我当前的代码进行了基准测试,并且与我当前功能的并行化没有任何好处。如果我添加到功能中,我将实现任务,因为它比我写的要简单得多。
猜你喜欢
  • 1970-01-01
  • 2012-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-27
  • 1970-01-01
  • 1970-01-01
  • 2014-08-09
相关资源
最近更新 更多