C # Сканирование дерева рекурсивно с несколькими потоками - PullRequest
0 голосов
/ 25 ноября 2018

Я сканирую каталог для поиска предметов.Я только что прочитал вопрос Многопоточная зацикливание каталогов в 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 для каждого элемента, и, как правило, это не кажется идеальным, но это было только для теста, так как рекламируются задачи для автоматического управления потоками ...

Этот метод работает, но он очень медленный.Для завершения требуется более 5 с , в то время как приведенная ниже однопоточная версия занимает около 0,5 с для завершения всей программы с одним и тем же набором данных:

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);
}

Iбродить, что на самом деле не так с методом кулакМашина не загружена, использование CUP незначительно, накопитель в порядке ... Я попытался профилировать его с помощью 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 раз медленнее?

I 'Я не уверен, как подойти к этому.Если я не хочу использовать задачи, нужно ли мне управлять потоками вручную или уже есть какая-то библиотека, которая бы подходила для этой работы?

1 Ответ

0 голосов
/ 26 ноября 2018

Я думаю, ты почти понял.Task.WaitAll(tks) это проблема.Вы блокируете один поток для этого, поскольку это синхронная операция.Вы скоро выйдете из потоков, все потоки просто ждут выполнения некоторых задач, для которых нет потоков.Вы можете решить это с асинхронностью, заменив ожидание на await Task.WhenAll(...).Это освободило бы нить, ожидая.С некоторой рабочей нагрузкой многопоточная версия значительно быстрее.Когда только IO связан, он примерно равен.

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");
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...