2 Вопрос: Вызов метода после завершения ThreadPool.QueueUserWorkItem

вопрос создан в Wed, May 8, 2019 12:00 AM

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

Примечание. Как вы, наверное, знаете, я не знаю, сколько потоков будет создано, потому что я не знаю, сколько там файлов. И установка тайм-аута не вариант.

    
1
2 ответа                              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());

и тогда поиск () становится

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

Эта функция DoTheJob () в идеале должна использовать асинхронный ввод-вывод, но в противном случае вы можете await Task.Run( () => DoTheJob(f))

    
1
2019-05-08 17: 36: 34Z
  1. Итак, если DoTheJob не является асинхронным методом, может ли это быть узким местом?
    2019-05-08 16: 15: 27Z
  2. Использование предоставленного вами решения занимает много времени, похоже на однопоточное задание. Без помощи задач и потоков для обхода всех файлов потребовалось около 8 минут, но с помощью ThreadPool это заняло около 1 минуты.
    2019-05-08 16: 19: 04Z
  3. Нет асинхронного API для получения списка объектов файловой системы в каталоге. С другой стороны, Task.Run не имеет контроля верхнего предела, который может легко привести к давлению пула потоков с большим количеством объектов файловой системы. Я бы предложил использовать API Parallel, который ориентирован на данные и имеет контроль параллелизма, или поток данных TPL, который больше подходит для нетривиальных потоков, которые могут подразумевать рекурсию.
    2019-05-08 16: 42: 06Z
  4. @ Hooman, чтобы эффективно использовать Parallel.ForEach, первая задача, которая должна быть решена, - каким-то образом избавиться от рекурсии.
    2019-05-08 17: 06: 25Z
  5. Он находится на ... (-: но я его отредактирую.
    2019-05-08 17: 35: 43Z

Вот пример того, как вы можете использовать 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 содержит операции, связанные с вводом-выводом, я бы посоветовал обработать его с await, поскольку Хенк Холтерман , предложенный как Parallel.ForEach, не зависит от нагрузки ввода-вывода.

    
0
2019-05-08 19: 04: 59Z
источник размещен Вот