【问题标题】:Call a method after ThreadPool.QueueUserWorkItem finishedThreadPool.QueueUserWorkItem 完成后调用方法
【发布时间】:2019-09-26 09:39:52
【问题描述】:

我正在开发一个用 c# 编写的控制台应用程序

此应用程序的目的是检查所有驱动器和文件并对其执行操作。但是用一个线程遍历所有文件是一个耗时的过程,这不是我的目标。

所以我决定使用ThreadPool 来处理它:

class Program () {
    static void Main(string[] args) {
        foreach (var d in DriveInfo.GetDrives()) {
            ThreadPool.QueueUserWorkItem(x => Search(d.RootDirectory.GetDirectories()));
        }

        Console.WriteLine("Job is done.");
        Console.ReadKey();
    }

    private static void Search(DirectoryInfo[] dirs) {
        foreach (var dir in dirs) {
            try {
                foreach (var f in dir.GetFiles()) {
                    ThreadPool.QueueUserWorkItem(x => DoTheJob(f));
                }

                ThreadPool.QueueUserWorkItem(x => Search(dir.GetDirectories()));
            } catch (Exception ex) {
                continue;
            }
        }
    }       
}

问题是Console.WriteLine("Job is done.") 在所有线程完成之前执行。我已经阅读了一些问题和答案,但没有一个能解决我的问题。

ThreadPool 中的所有线程完成工作后如何调用方法?

注意:您可能知道,我不知道会创建多少线程,因为我不知道有多少文件。并且设置超时不是一种选择。

【问题讨论】:

    标签: c# multithreading threadpool queueuserworkitem file-traversal


    【解决方案1】:

    以下是如何使用Parallel.ForEach 产生公平负载的示例:

    static IEnumerable<FileSystemInfo> GetFileSystemObjects(DirectoryInfo dirInfo)
    {
        foreach (var file in dirInfo.GetFiles())
            yield return file;
    
        foreach (var dir in dirInfo.GetDirectories())
        {
            foreach (var fso in GetFileSystemObjects(dir))
                yield return fso;
            yield return dir;
        }
    }
    
    static void Main(string[] args)
    {
        var files = GetFileSystemObjects(new DirectoryInfo(<some path>)).OfType<FileInfo>();
    
        Parallel.ForEach(files, f =>
        {
            DoTheJob(f);
        });
    }
    

    如果 DoTheJob 包含 I/O 绑定操作,我会考虑使用 await 处理它,因为 Henk Holterman 建议 Parallel.ForEach 与 I/O 负载无关。

    【讨论】:

      【解决方案2】:

      使用 QueueUserWorkItem() 是低级的准系统方法。如果无法控制您的工作,那就是一劳永逸。

      Tasks 运行在 ThreadPool 之上,async/await 可以在这里解决您的问题。

      顶层:

      var tasks = new List<Task>();
      foreach (var d in DriveInfo.GetDrives())
      {
          tasks.Add( Search(d.RootDirectory.GetDirectories()));
      }
      Task.WaitAll(tasks.ToArray());
      

      然后你 Search() 变成了

      private static async Task Search(DirectoryInfo[] dirs)
      {
          ... 
          foreach(...)
          {
              await Task.Run(...);
          } 
          await Search(dir.GetDirectories());
      }
      

      DoTheJob() 最好使用异步 I/O,否则你可以await Task.Run( () =&gt; DoTheJob(f))

      【讨论】:

      • 那么如果DoTheJob 不是异步方法,这会是瓶颈吗?
      • 使用您提供的解决方案需要很长时间,似乎是一个单线程工作。如果没有任务和线程的帮助,遍历所有文件大约需要 8 分钟,但使用 ThreadPool 大约需要 1 分钟。
      • 但是在那一分钟之后真正完成了多少?
      • 没有用于获取目录中文件系统对象列表的异步 API。另一方面,Task.Run 没有上限控制,这很容易导致具有大量文件系统对象的线程池压力。我的建议是使用Parallel API,它是面向数据的并且可以控制并行性,或者是 TPL Dataflow,它更适合可能意味着递归的非平凡流。
      • @Hooman,为了有效地利用Parallel.ForEach,首先要解决的问题就是以某种方式摆脱递归。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-10
      • 2017-08-16
      • 1970-01-01
      相关资源
      最近更新 更多