【问题标题】:Wait for all threads to finish等待所有线程完成
【发布时间】:2013-12-05 18:57:58
【问题描述】:

我想在 C# 中处理子目录和文件的文件系统/文件夹。我正在使用 TPL 库中的任务。这个想法是递归地执行它并为每个文件夹创建一个任务。主线程应该等待子线程完成,然后打印一些信息。事实上,我只想知道扫描何时完成。我从线程池开始,然后切换到 TLP。做了一些简单的例子。经过从简单代码到越来越臃肿的代码的一些尝试后,我被困在这里:

private Logger log = LogManager.GetCurrentClassLogger();

public MediaObjectFolder MediaObjectFolder { get; set; }
private Queue<MediaObjectFolder> Queue { get; set; }

private object quelock, tasklock;
private List<Task> scanTasks;

public IsoTagger()
{
    quelock = new object();
    tasklock = new object();
    scanTasks = new List<Task>();

    MediaObjectFolder = new MediaObjectFolder(@"D:\Users\Roman\Music\Rock\temp");
    Queue = new Queue<MediaObjectFolder>();
}

public MediaObject RescanFile(string fullpath, string filename)
{
    return new MediaObject(fullpath);
}

public void Rescan()
{
    Queue.Clear();

    lock (tasklock)
    {
        Task scanFolderTask = Task.Factory.StartNew(ScanFolder, MediaObjectFolder);
        scanTasks.Add(scanFolderTask);
    }

    Task.Factory.ContinueWhenAll(scanTasks.ToArray(), (ant) =>
        {
            if (log != null)
            {
                log.Debug("scan finished");
                log.Debug("number of folders: {0}", Queue.Count);
            }

        });
}

private void ScanFolder(object o)
{
    List<Task> subTasks = new List<Task>();

    MediaObjectFolder mof = o as MediaObjectFolder;
    log.Debug("thread - " + mof.Folder);

    string[] subdirs = Directory.GetDirectories(mof.Folder);
    string[] files = Directory.GetFiles(mof.Folder, "*.mp3");


    foreach(string dir in subdirs)
    {
        log.Debug(dir);

        MediaObjectFolder tmp = new MediaObjectFolder(dir);
        lock (tasklock)
        {
            Task tmpTask = new Task(ScanFolder, tmp);
            subTasks.Add(tmpTask);
        }
    }

    foreach (Task tsk in subTasks)
    {
        tsk.Start();
    }

    foreach (string file in files)
    {
        log.Debug(file);

        MediaObject tmp = new MediaObject(file);
        MediaObjectFolder.MediaObjects.Add(tmp);
    }

    lock (quelock)
    {
        Queue.Enqueue(mof);
    }

    if (subTasks != null)
        Task.Factory.ContinueWhenAll(subTasks.ToArray(), logTask => log.Debug("thread release - " + mof.Folder));
}

主线程有时仍然过早地继续,而不是在完成所有其他线程之后。 (我对 C# 比较陌生,也不是并行编程方面的专家,所以可能存在一些重量级概念错误)

【问题讨论】:

    标签: c# .net multithreading task-parallel-library


    【解决方案1】:

    您所采用的一般方法本质上使这个问题很难解决。相反,您可以简单地使用文件系统方法为您遍历层次结构,然后使用 PLINQ 有效地并行处理这些文件:

    var directories = Directory.EnumerateDirectories(path, "*"
        , SearchOption.AllDirectories);
    
    var query = directories.AsParallel().Select(dir =>
    {
        var files = Directory.EnumerateFiles(dir, "*.mp3"
            , SearchOption.TopDirectoryOnly);
        //TODO create custom object and add files
    });
    

    【讨论】:

    • 我来看看。它适用于很多目录吗?我的意思是我对大约 3000 个目录中的 +25000 个文件感兴趣。但我也会看看目录功能......
    • @rome 当然会。根据请求,这些目录会被延迟枚举,并基于将优化吞吐量的调度程序进行并行化。
    • 我正在尝试...谢谢!一旦它起作用,我会将其标记为答案;-)
    • 感谢您的建议,我终于想出了自己的解决方案,但是目录和文件枚举的提示非常有价值
    【解决方案2】:

    您需要研究 Task.WaitAll 和 Task.WaitAny 方法。这里有示例代码:msdn.microsoft.com

    快速回答:

    Task.WaitAll(subTasks);
    

    应该适合你。

    【讨论】:

    • 仅靠这些是不够的。他不知道,刚开始的时候,所有需要等待的任务是什么。
    • 他只需要等待所有任务的start()完成后,即递归函数返回后。否则,他仍在创建新任务,还不关心已完成的任务。
    • 但是那个函数只完成启动任务;当它返回时它们还没有完成,并且这些任务将开始更多个任务,这要归功于递归。您的建议将确保处理所有顶级目录,但不处理子目录。
    • 那么你就失去了所有的异步性,这是首先使用任务的关键(以避免阻塞等待)。无论如何,这个答案是完全不充分的。正如我在第一条评论中所说,虽然这是一个可用于解决问题的工具,但在这种情况下应该如何使用它并不明显,因此需要一个答案来描述它是如何使用的应该使用。
    • 我刚刚看到了您的解决方案。对于给定的问题要好得多!也感谢您的建设性批评。我很感激。
    【解决方案3】:

    在 Servy 提出了很好的建议并进一步研究了 C# 中的并行性之后,我想出了我的问题的答案。 因为我真的不需要 LINQ 来完成这个简单的任务,我只想枚举我的文件系统并并行处理文件夹。

    public void Scan()
    {
        // ...
        // enumerate all directories under one root folder (mof.Folder)
        var directories = Directory.EnumerateDirectories(mof.Folder, "*", SearchOption.AllDirectories);
        // use parallel foreach from TPL to process folders
        Parallel.ForEach(directories, ProcessFolder);
        // ...
    }
    
    private void ProcessFolder(string folder)
    {
        if (!Directory.Exists(folder))
        {
            throw new ArgumentException("root folder does not exist!");
        }
        MediaObjectFolder mof = new MediaObjectFolder(folder);
        IEnumerable<string> files = Directory.EnumerateFiles(folder, "*.mp3", SearchOption.TopDirectoryOnly);
        foreach (string file in files)
        {
            MediaObject mo = new MediaObject(file);
            mof.MediaObjects.Add(mo);
        }
        lock (quelock)
        {
             // add object to global queue
             Enqueue(mof);
        }
    }
    

    经过深入研究后,我发现这是最简单的解决方案。请注意:如果这种方法更快,我还没有做任何测试,因为我在一个不是很大的临时文件库上工作。这也是 MSDN 库中描述的用于文件系统并行处理的方式。

    PS:性能还有很大的提升空间

    【讨论】:

      猜你喜欢
      • 2010-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-30
      • 2015-04-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多