Как выполнить рекурсивный поиск папок и файлов с использованием очередей производителя / потребителя?

#c# #multithreading #producer-consumer

#c# #многопоточность #производитель-потребитель

Вопрос:

Я хотел бы сначала выполнить поиск по каталогу, а затем по файлам внутри него, по ключевому слову.

Я знаю, что мне нужны два класса, класс производителя и класс потребителя, но я не знаю, как выполнить поиск с использованием очереди производителя / потребителя c #?

 public class Program
{
    private static void Main()
    {
        Queue<File> searchFile = new Queue<File>();
        Queue<Directory> searchDirectory = new Queue<Directory>();

        new Thread(searchDirectory).Start();

        for (int i = 0; i < 3; i  )
            new Thread(searchFile).Start();
    }
}
  

Комментарии:

1. Это действительно отличный вопрос.

Ответ №1:

Начальные проблемы:

  1. Вы объявляете 2 переменные разных типов, используя одно и то же имя переменной с одинаковой областью видимости.
  2. Вы не хотите запускать потоковый поиск в каталоге, а другой — в файле.

Проблема с пунктом № 2 заключается в том, что вы работаете с одним из самых больших узких мест с несколькими потоками — то есть с дисковым вводом-выводом. Вы ничего не получите, выполняя дисковый ввод-вывод (на стандартном жестком диске), реализуя более 1 рабочего потока.

Объясните подробнее, что вы пытаетесь сделать (с примером, пожалуйста). Может быть лучший процесс.

Ответ №2:

Во-первых, Directory это статический класс, поэтому вы не сможете использовать его в коллекции. Вместо этого вам нужно будет использовать DirectoryInfo . Во-вторых, я бы использовал единую очередь, которая будет содержать DirectoryInfo экземпляры. Затем файлы могут быть перечислены как часть обработки одной папки.

Вот как я бы это сделал, используя шаблон производитель-потребитель. Эта реализация использует BlockingCollection класс which в качестве реализации блокирующей очереди. Блокирующие очереди весьма полезны в шаблоне производитель-потребитель, потому что они абстрагируют почти все сведения о производителе-потребителе.

 public class Searcher
{
  private BlockingCollection<DirectoryInfo> m_Queue = new BlockingCollection<DirectoryInfo>();

  public Searcher()
  {
    for (int i = 0; i < NUMBER_OF_THREADS; i  )
    {
      var thread = new Thread(Run);
      thread.IsBackground = true;
      thread.Start();
    }
  }

  public void Search(DirectoryInfo root)
  {
    m_Queue.Add(root);
  }

  private void Run()
  {
    while (true)
    {
      // Wait for an item to appear in the queue.
      DirectoryInfo root = m_Queue.Take();

      // Add each child directory to the queue. This is the recursive part.
      foreach (DirectoryInfo child in root.GetDirectories())
      {
        m_Queue.Add(child);
      }

      // Now we can enumerate each file in the directory.
      foreach (FileInfo child in root.GetFiles())
      {
        // Add your search logic here.
      }
    }
  }
}
  

Я должен отметить, что большинство дисков работают более сериализованным образом, поэтому наличие нескольких потоков, пытающихся выполнить поиск по файлам, может не принести вам много пользы, если только часть вашей логики, привязанная к процессору, не является обширной.

Ответ №3:

Как предполагают другие постеры, несколько потоков, пытающихся выполнить ввод-вывод, вызовут проблемы. Однако их можно было бы использовать для создания полной очереди директорий (если она была очень глубокой), а затем отдельного потока для выполнения регулярных выражений в файле. Примерно так:

     class Program
{
    static void Main(string[] args)
    {
        ConcurrentQueue<DirectoryInfo> concurrentQueue = new ConcurrentQueue<DirectoryInfo>();
        GetAllDirectories(new DirectoryInfo(@"C:localoracle"), concurrentQueue);
        Action action = () =>{
            const string toFind = "ora";
            DirectoryInfo info;
            while(concurrentQueue.TryDequeue(out info))
            {
                FindInFile(toFind, info);
            }
        };

        Parallel.Invoke(action, action, action, action);
        Console.WriteLine("total found "   _counter);
        Console.ReadKey();
    }
    static int _counter = 0;
    static void FindInFile(string textToFind,DirectoryInfo dirInfo)
    {
        var files =dirInfo.GetFiles();
        foreach(FileInfo file in files)
        {
            using (StreamReader reader = new StreamReader(file.FullName))
            {
                string content = reader.ReadToEnd();

                Match match = Regex.Match(content, textToFind, RegexOptions.Multiline);

                if(match.Success)
                {
                    Interlocked.Increment(ref _counter);
                    Console.WriteLine(file.FullName   " found "   match.Captures.Count);

                    foreach(var t in match.Captures)
                    {
                        Console.WriteLine("-------------> char index"   match.Index);
                    }
                }
            }
        }
    }

    internal static void GetAllDirectories(DirectoryInfo root, ConcurrentQueue<DirectoryInfo> values)
    {
        foreach (var di in root.GetDirectories())
        {
            GetAllDirectories(di, values);
            values.Enqueue(di);
        }
    }
}
  

Ответ №4:

Я отредактировал сообщение (которое ожидает рецензирования). Если это будет одобрено, я отредактировал код, чтобы исправить основную проблему области видимости и опечаток, но я не думаю, что вы готовы к многопоточности, не говоря уже об очередях производителя-потребителя (Бог свидетель, я какое-то время баловался многопоточностью, и в конечном итоге я все еще путаю свои реализации, но это, вероятно, только я!).

Сначала вы должны освоиться с областями видимости и многопоточностью. Особенно прочитайте о проблемах механизма блокировки / параллелизма, которые имеют решающее значение для реализации успешного многопоточного решения.

Во-вторых, как предлагает IAbstract, действительно реализуйте несколько потоков с помощью мьютексов / семафоров, чтобы повысить производительность при многопоточности, а также получить желаемую очередь производителя-потребителя.

Также, если вам удобно, вы также можете посмотреть последнюю версию библиотеки потоков данных Async CTP1, которая имеет последнюю поддержку этого шаблона с использованием библиотеки Tasks Parallel. В качестве альтернативы вы можете использовать BlockingCollection для реализации этого шаблона.

У Stackoverflow также есть вопросы, связанные с вашим вопросом, с некоторыми отличными ответами. Просто найдите «производитель-потребитель», чтобы прочитать их