代码之家  ›  专栏  ›  技术社区  ›  Paweł Audionysos

C#使用多线程递归扫描树

  •  0
  • Paweł Audionysos  · 技术社区  · 7 年前

    我正在扫描一些目录中的项目。我刚读过 Multithreaded Directory Looping in C# 问题,但我还是想让它成为多重的。尽管每个人都说驱动器将成为瓶颈,但我有一些观点:

    • 这些驱动器可能大多是“单线程”的,但您如何知道它们将来会带来什么呢?
    • 如何知道正在扫描的不同子路径是同一个物理驱动器?
    • 我使用一个抽象层(甚至两个)覆盖 System.IO 这样我以后可以在不同的场景中重用代码。

    任务 第一个虚拟实现是:

    public async Task Scan(bool recursive = false) {
        var t = new Task(() => {
            foreach (var p in path.scan) Add(p);
            if (!recursive) return;
            var tks = new Task[subs.Count]; var i = 0;
            foreach (var s in subs) tks[i++] = s.Scan(true);
            Task.WaitAll(tks);
        }); t.Start();
        await t;
    }
    

    我不喜欢创建一个 Task

    这种方法有效,但速度很慢。这需要更多的时间 以完成,而下面的单一威胁版本将 0.5s 要在同一数据集上完成整个程序:

    public void Scan2(bool recursive = false) {
        foreach (var p in path.scan) Add(p);
        if (!recursive) return;
        foreach (var s in subs) s.Scan2(true);
    }
    

    我不知道拳头法到底出了什么问题。机器没有负载,杯子使用量很小,驱动很好。。。我试着用NProfiler分析它,除了程序本身,它不会告诉我太多 Task.WaitAll(tks)

    我还编写了一个线程锁定计数机制,在添加每个项时调用该机制。也许是它的问题?

    #region SubCouting
    public Dictionary<Type, int> counters = new Dictionary<Type, int>(); 
    private object cLock = new object();
    private int _sc = 0;
    public int subCount => _sc;
    private void inCounter(Type t) {
        lock (cLock) {
            if (!counters.ContainsKey(t)) counters.Add(t, 1);
            counters[t]++;
            _sc++;
        }
        if (parent) parent.inCounter(t);
    }
    #endregion
    

    但是,即使线程在这里等待,执行时间是否会与单线程版本类似,而不是慢10倍?

    我不知道该怎么做。如果我不想使用任务,我是否需要手动管理线程,或者是否已经有一些库可以很好地适合这项工作?

    1 回复  |  直到 7 年前
        1
  •  1
  •   Antonín Lejsek    7 年前

    我想你差不多成功了。 Task.WaitAll(tks) 这就是问题所在。由于这是一个同步操作,因此可以为此阻止一个线程。您很快就会退出线程,所有线程都在等待一些没有线程可运行的任务。您可以使用async解决此问题,将等待替换为 await Task.WhenAll(...)

    ConcurrentBag<string> result = new ConcurrentBag<string>();
    List<string> result2 = new List<string>();
    
    public async Task Scan(string path)
    {
        await Task.Run(async () =>
        {
            var subs = Directory.GetDirectories(path);
            await Task.WhenAll(subs.Select(s => Scan(s)));
    
            result.Add(Enumerable.Range(0, 1000000).Sum(i => path[i % path.Length]).ToString());
        });
    }
    
    public void Scan2(string path)
    {
        result2.Add(Enumerable.Range(0, 1000000).Sum(i => path[i % path.Length]).ToString());
    
        var subs = Directory.GetDirectories(path);
        foreach (var s in subs) Scan2(s);
    }
    
    private async void button4_Click(object sender, EventArgs e)
    {
        string dir = @"d:\tmp";
    
        System.Diagnostics.Stopwatch st = new System.Diagnostics.Stopwatch();
        st.Start();
        await Scan(dir);
        st.Stop();
        MessageBox.Show(st.ElapsedMilliseconds.ToString());
    
        st = new System.Diagnostics.Stopwatch();
        st.Start();
        Scan2(dir);            
        st.Stop();
        MessageBox.Show(st.ElapsedMilliseconds.ToString());
    
        MessageBox.Show(result.OrderBy(x => x).SequenceEqual(result2.OrderBy(x => x)) ? "OK" : "ERROR");
    }