等待所有线程完成

时间:2013-12-05 18:57:59

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

我想在C#中处理子目录和文件的文件系统/文件夹。我正在使用TPL库中的任务。我们的想法是递归地执行它并为每个文件夹创建任务。主线程应该等待子线程完成然后打印一些信息。事实上,我只想知道扫描何时完成。我已经开始使用threadpool,然后切换到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#比较新,也不是并行编程方面的专家,因此可能存在一些重量级的概念错误)

3 个答案:

答案 0 :(得分:4)

你所采取的一般方法使得这个问题难以解决。相反,您可以简单地使用文件系统方法遍历层次结构,然后使用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
});

答案 1 :(得分:0)

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

快速回答:

Task.WaitAll(subTasks);

应该适合你。

答案 2 :(得分:0)

在经过Servy的好建议并进一步研究C#中的Parallelism之后,我想出了一个问题的答案。 因为我不需要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:还有很多改进性能的空间