Czy istnieje szybszy sposób na znalezienie wszystkich plików w katalogu i wszystkich podkatalogach?

Piszę program, który musi przeszukać katalog i wszystkie jego podkatalogi w poszukiwaniu plików, które mają określone rozszerzenie. To będzie używane zarówno na dysku lokalnym, jak i sieciowym, więc wydajność jest trochę problemem.

Oto metoda rekurencyjna, której teraz używam:

private void GetFileList(string fileSearchPattern, string rootFolderPath, List<FileInfo> files)
{
    DirectoryInfo di = new DirectoryInfo(rootFolderPath);

    FileInfo[] fiArr = di.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly);
    files.AddRange(fiArr);

    DirectoryInfo[] diArr = di.GetDirectories();

    foreach (DirectoryInfo info in diArr)
    {
        GetFileList(fileSearchPattern, info.FullName, files);
    }
}

Mógłbym ustawić SearchOption na AllDirectories i nie używać metody rekurencyjnej, ale w przyszłości będę chciał wstawić jakiś kod, aby powiadomić użytkownika, który folder jest obecnie zeskanowane.

Podczas tworzenia listy obiektów FileInfo teraz wszystko, na czym mi zależy, to ścieżki do plików. Będę miał istniejącą listę plików, którą chcę porównać z nową listą plików, aby zobaczyć, które pliki zostały dodane lub usunięte. Czy istnieje jakiś szybszy sposób na wygenerowanie tej listy ścieżek plików? Czy jest coś, co mogę zrobić, aby zoptymalizować wyszukiwanie plików wokół zapytań o pliki na współdzielonym dysku sieciowym?


Update 1

Próbowałem stworzyć nie-rekurencyjna metoda, która robi to samo, najpierw znajdując wszystkie podkatalogi, a następnie iteracyjnie skanując każdy katalog w poszukiwaniu plików. Oto metoda:

public static List<FileInfo> GetFileList(string fileSearchPattern, string rootFolderPath)
{
    DirectoryInfo rootDir = new DirectoryInfo(rootFolderPath);

    List<DirectoryInfo> dirList = new List<DirectoryInfo>(rootDir.GetDirectories("*", SearchOption.AllDirectories));
    dirList.Add(rootDir);

    List<FileInfo> fileList = new List<FileInfo>();

    foreach (DirectoryInfo dir in dirList)
    {
        fileList.AddRange(dir.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly));
    }

    return fileList;
}

Update 2

W porządku, więc przeprowadziłem kilka testów na lokalnym i zdalnym folderze, z których oba mają dużo plików (~1200). Oto metody, na których przeprowadziłem testy. Wyniki są poniżej.

  • GetFileListA () : rozwiązanie Nierekurencyjne w powyższej aktualizacji. Myślę, że to odpowiednik rozwiązania Jaya.
  • GetFileListB () : metoda rekurencyjna z pierwotnego pytania
  • GetFileListC () : pobiera wszystkie katalogi ze statycznym katalogiem.Metoda GetDirectories (). Następnie pobiera wszystkie ścieżki plików ze statycznym katalogiem.Metoda GetFiles (). Wypełnia i zwraca listę
  • GetFileListD () : rozwiązanie marca Gravella za pomocą kolejki i zwraca IEnumberable. Wypełniłem listę z wynikowymi IEnumerable
    • DirectoryInfo.GetFiles : nie utworzono dodatkowej metody. Utworzono instancję DirectoryInfo ze ścieżki folderu głównego. Wywołane GetFiles za pomocą SearchOption.AllDirectories
  • Katalog.GetFiles : nie utworzono dodatkowej metody. Wywołana statyczną metodą GetFiles katalogu za pomocą searchoption.AllDirectories
Method                       Local Folder       Remote Folder
GetFileListA()               00:00.0781235      05:22.9000502
GetFileListB()               00:00.0624988      03:43.5425829
GetFileListC()               00:00.0624988      05:19.7282361
GetFileListD()               00:00.0468741      03:38.1208120
DirectoryInfo.GetFiles       00:00.0468741      03:45.4644210
Directory.GetFiles           00:00.0312494      03:48.0737459

. . wygląda na to, że Marc jest najszybszy.

Author: cramopy, 2010-01-21

12 answers

Wypróbuj tę wersję bloku iteratora, która unika rekurencji i obiektów Info:

public static IEnumerable<string> GetFileList(string fileSearchPattern, string rootFolderPath)
{
    Queue<string> pending = new Queue<string>();
    pending.Enqueue(rootFolderPath);
    string[] tmp;
    while (pending.Count > 0)
    {
        rootFolderPath = pending.Dequeue();
        try
        {
            tmp = Directory.GetFiles(rootFolderPath, fileSearchPattern);
        }
        catch (UnauthorizedAccessException)
        {
            continue;
        }
        for (int i = 0; i < tmp.Length; i++)
        {
            yield return tmp[i];
        }
        tmp = Directory.GetDirectories(rootFolderPath);
        for (int i = 0; i < tmp.Length; i++)
        {
            pending.Enqueue(tmp[i]);
        }
    }
}

Zauważ również, że 4.0 ma wbudowane wersje bloków iteratora(EnumerateFiles, EnumerateFileSystemEntries) to może być szybsze (bardziej bezpośredni dostęp do systemu plików; mniej tablic)

 39
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-17 05:15:16

Fajne pytanie.

Pobawiłem się trochę i wykorzystując bloki iteratora i LINQ wydaje mi się, że poprawiłem Twoją poprawioną implementację o około 40%

Chciałbym, aby przetestować go za pomocą metod pomiaru czasu i w sieci, aby zobaczyć, jak wygląda różnica.

Oto mięso tego

private static IEnumerable<FileInfo> GetFileList(string searchPattern, string rootFolderPath)
{
    var rootDir = new DirectoryInfo(rootFolderPath);
    var dirList = rootDir.GetDirectories("*", SearchOption.AllDirectories);

    return from directoriesWithFiles in ReturnFiles(dirList, searchPattern).SelectMany(files => files)
           select directoriesWithFiles;
}

private static IEnumerable<FileInfo[]> ReturnFiles(DirectoryInfo[] dirList, string fileSearchPattern)
{
    foreach (DirectoryInfo dir in dirList)
    {
        yield return dir.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly);
    }
}
 6
Author: Brad Cunningham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-24 13:23:25

Krótka odpowiedź jak poprawić wydajność tego kodu brzmi: nie możesz.

Prawdziwym uderzeniem wydajności jest rzeczywiste opóźnienie dysku lub sieci, więc bez względu na to, w jaki sposób go przewrócisz, musisz sprawdzić i iterację każdego elementu pliku i pobrać listę katalogów i plików. (To oczywiście wyklucza modyfikacje sprzętu lub sterowników w celu zmniejszenia lub poprawy opóźnienia dysku, ale wiele osób już płaci dużo pieniędzy, aby rozwiązać te problemy, więc będziemy ignoruj tę stronę na razie)

Biorąc pod uwagę oryginalne ograniczenia, istnieje już kilka rozwiązań, które mniej lub bardziej elegancko owijają proces iteracji (jednak, ponieważ zakładam, że czytam z jednego dysku twardego, równoległość nie pomoże szybciej poprzeć drzewa katalogów, a może nawet wydłużyć ten czas, ponieważ teraz masz dwa lub więcej wątków walczących o dane na różnych częściach dysku, gdy próbuje szukać Wstecz i czwarty) zmniejsz liczbę wątków o więcej niż jeden. wytworzonych obiektów, itp. Jednak jeśli ocenimy, w jaki sposób funkcja będzie zużywana przez dewelopera końcowego, możemy wymyślić pewne optymalizacje i uogólnienia.

Po pierwsze, możemy opóźnić wykonanie performance zwracając IEnumerable, yield return osiąga to poprzez kompilację w maszynie stanowej enumerator wewnątrz anonimowej klasy, która implementuje IEnumerable i jest zwracana, gdy metoda jest wykonywana. Większość metod w LINQ jest pisana w celu opóźnienia wykonania dopóki iteracja nie zostanie wykonana, więc kod w select lub SelectMany nie zostanie wykonany, dopóki nie zostanie iteracją IEnumerable. Efekt końcowy opóźnionej realizacji jest odczuwalny tylko wtedy, gdy musisz pobrać podzbiór danych w późniejszym czasie, na przykład, jeśli potrzebujesz tylko pierwszych 10 wyników, opóźnienie wykonania zapytania, które zwraca kilka tysięcy wyników, nie będzie powtarzane przez całe 1000 wyników, dopóki nie potrzebujesz więcej niż dziesięciu.

Teraz, biorąc pod uwagę, że chcesz zrobić podfolder Szukaj, mogę również wywnioskować, że może to być przydatne, jeśli możesz określić tę głębokość, a jeśli to zrobię, to również uogólnia mój problem, ale także wymaga rekurencyjnego rozwiązania. Później, gdy ktoś zdecyduje, że teraz musi przeszukać dwa katalogi głęboko, ponieważ zwiększyliśmy liczbę plików i zdecydowaliśmy się dodać kolejną warstwę kategoryzacji , możesz po prostu dokonać niewielkiej modyfikacji zamiast przepisywać funkcję.

W świetle tego wszystkiego, oto rozwiązanie, które przyszedłem up with that provides a bardziej general solution than some of the others above:

public static IEnumerable<FileInfo> BetterFileList(string fileSearchPattern, string rootFolderPath)
{
    return BetterFileList(fileSearchPattern, new DirectoryInfo(rootFolderPath), 1);
}

public static IEnumerable<FileInfo> BetterFileList(string fileSearchPattern, DirectoryInfo directory, int depth)
{
    return depth == 0
        ? directory.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly)
        : directory.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly).Concat(
            directory.GetDirectories().SelectMany(x => BetterFileList(fileSearchPattern, x, depth - 1)));
}

Na marginesie, coś jeszcze, o czym nikt dotąd nie wspomniał, to uprawnienia i bezpieczeństwo plików. Obecnie nie ma żądań sprawdzania, obsługi ani uprawnień, a kod wyrzuci wyjątki uprawnień do plików, jeśli napotka katalog, do którego nie ma dostępu.

 5
Author: Paul Rohde,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-24 14:10:26

Uzyskanie 2 milionów nazw plików zgodnych z filtrem zajmuje 30 sekund. Powodem, dla którego jest to tak szybkie, jest to, że wykonuję tylko 1 wyliczenie. Każde dodatkowe wyliczenie wpływa na wydajność. Zmienna długość jest otwarta dla Twojej interpretacji i niekoniecznie związana z przykładem wyliczenia.

if (Directory.Exists(path))
{
    files = Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories)
    .Where(s => s.EndsWith(".xml") || s.EndsWith(".csv"))
    .Select(s => s.Remove(0, length)).ToList(); // Remove the Dir info.
}
 3
Author: Kentonbmax,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-18 14:53:34

Rozważ podzielenie zaktualizowanej metody na dwa Iteratory:

private static IEnumerable<DirectoryInfo> GetDirs(string rootFolderPath)
{
     DirectoryInfo rootDir = new DirectoryInfo(rootFolderPath);
     yield return rootDir;

     foreach(DirectoryInfo di in rootDir.GetDirectories("*", SearchOption.AllDirectories));
     {
          yield return di;
     }
     yield break;
}

public static IEnumerable<FileInfo> GetFileList(string fileSearchPattern, string rootFolderPath)
{
     var allDirs = GetDirs(rootFolderPath);
     foreach(DirectoryInfo di in allDirs())
     {
          var files = di.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly);
          foreach(FileInfo fi in files)
          {
               yield return fi;
          }
     }
     yield break;
}

Ponadto, w zależności od sytuacji sieciowej, gdybyś był w stanie zainstalować małą usługę na tym serwerze, do której można było zadzwonić z KOMPUTERA Klienta, zbliżyłbyś się znacznie do wyników "folderu lokalnego", ponieważ wyszukiwanie mogłoby wykonać na serwerze i po prostu zwrócić Wyniki do ciebie. Będzie to największe zwiększenie prędkości w scenariuszu folderu sieciowego, ale może nie być dostępne w Twoim sytuacja. Używałem programu do synchronizacji plików, który zawiera tę opcję - Po zainstalowaniu usługi na moim serwerze program stał się sposób {5]} szybciej w identyfikacji plików, które były nowe, usunięte i out-of-sync.

 1
Author: Jay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-21 06:17:19

Spróbuj programowania równoległego:

private string _fileSearchPattern;
private List<string> _files;
private object lockThis = new object();

public List<string> GetFileList(string fileSearchPattern, string rootFolderPath)
{
    _fileSearchPattern = fileSearchPattern;
    AddFileList(rootFolderPath);
    return _files;
}

private void AddFileList(string rootFolderPath)
{
    var files = Directory.GetFiles(rootFolderPath, _fileSearchPattern);
    lock (lockThis)
    {
        _files.AddRange(files);
    }

    var directories = Directory.GetDirectories(rootFolderPath);

    Parallel.ForEach(directories, AddFileList); // same as Parallel.ForEach(directories, directory => AddFileList(directory));
}
 1
Author: Jaider,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-21 18:27:47

Wydaje się, że DirectoryInfo daje znacznie więcej informacji niż potrzebujesz, spróbuj przesłać polecenie dir i przetworzyć informacje z tego.

 1
Author: user2385360,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-15 10:07:10

Metody BCL są przenośne, że tak powiem. Jeśli pozostaniemy w 100% zarządzani, uważam, że najlepsze, co możesz zrobić, to wywołać GetDirectories / foldery podczas sprawdzania praw dostępu (lub ewentualnie nie sprawdzać praw i mieć inny wątek gotowy do pracy, gdy pierwszy trwa trochę zbyt długo - znak, że ma zamiar wyrzucić wyjątek nieautoryzowany dostęp - może to być unikalne z filtrami WYJĄTKÓW za pomocą VB lub od dziś niepublikowane c#).

Jeśli chcesz szybciej niż GetDirectories musisz wywołanie win32 (findsomethingEx itp.), które dostarcza określone flagi, które pozwalają ignorować ewentualnie niepotrzebne IO podczas przechodzenia struktur MFT. Również jeśli dysk jest udział sieciowy, nie może być duże przyspieszenie przez podobne podejście, ale tym razem unikając również nadmierne sieci roundtrips.

Teraz, jeśli masz admina i używasz ntfs i naprawdę się spieszysz z milionami plików do przejrzenia, absolutnym najszybszym sposobem, aby przejść przez nie( zakładając, że wirujący rust zabija opóźnienie dysku) jest wykorzystanie zarówno MFT, jak i journalingu w połączeniu, zasadniczo zastępując usługę indeksowania usługą dostosowaną do konkretnych potrzeb. Jeśli musisz znaleźć tylko nazwy plików, a nie rozmiary (lub rozmiary też, ale musisz je buforować i używać dziennika, aby zauważyć zmiany), takie podejście może pozwolić na praktycznie natychmiastowe wyszukiwanie dziesiątek milionów plików i folderów, jeśli zostanie wdrożone idealnie. Może być jeden lub dwa payware, które zawracały sobie tym głowę. Są próbki zarówno MFT (DiscUtils) jak i czytanie dziennika (google) w C#. Mam tylko około 5 milionów plików i samo użycie NTFSSearch jest wystarczająco dobre dla tej kwoty, ponieważ wyszukiwanie zajmuje około 10-20 sekund. Po dodaniu odczytu z dziennika zmniejszy to się do

 1
Author: Anonymous Coward,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-02 19:28:35

Możesz użyć parallel foreach (. Net 4.0) lub spróbować Poor Man ' s Parallel.Foreach Iterator dla .Net3.5 . To przyspieszy twoje poszukiwania.

 0
Author: ata,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-21 06:05:37

To jest straszne, a powodem, dla którego wyszukiwanie plików jest straszne na platformach Windows, jest to, że MS popełnił błąd, który wydaje się niechętny do naprawienia. Powinieneś być w stanie użyć SearchOption.AllDirectories I wszyscy odzyskamy prędkość, jakiej chcemy. Ale nie możesz tego zrobić, ponieważ GetDirectories wymaga oddzwonienia, abyś mógł zdecydować, co zrobić z katalogami, do których nie masz dostępu. MS zapomniał lub nie pomyślał, aby przetestować klasę na własnych komputerach.

Więc my wszystkie zostały z nonsensownymi pętlami rekurencyjnymi.

W C# / Managed C++ masz bardzo mało oprionów, są to również opcje, które biorą MS, ponieważ ich kodery też nie wypracowały, jak to obejść.

Najważniejsze jest to, że elementy wyświetlania, takie jak TreeViews i FileViews, wyszukują tylko i pokazują to, co użytkownicy mogą zobaczyć. Na kontrolkach znajduje się wiele pomocników, w tym wyzwalacze, które informują, kiedy trzeba wypełnić niektóre dane.

W drzewach, począwszy od tryb zwinięty, przeszukuje ten jeden katalog jak i kiedy użytkownik otworzy go w drzewie, to znacznie szybciej niż oczekiwanie na wypełnienie całego drzewa. To samo w przeglądach plików, mam tendencję do reguły 10%, jak wiele elementów mieści się w obszarze wyświetlania, ma inne 10% gotowe, jeśli użytkownik przewija, jest ładnie responsywny.

MS do pre-search and directory watch. Mała baza katalogów, plików, oznacza to, że masz na swoim drzewie itp mają dobry szybki punkt startowy, spada w dół trochę na odświeżaniu.

Ale wymieszaj te dwa pomysły, weź katalogi i pliki z bazy danych, ale odśwież wyszukiwanie, ponieważ węzeł drzewa jest rozszerzony (tylko ten węzeł drzewa) i jako inny katalog jest zaznaczony w drzewie.

Ale lepiej jest dodać system wyszukiwania plików jako usługę. MS już to ma, ale z tego co wiem nie mamy do niego dostępu, podejrzewam, że to dlatego, że jest odporny na błędy "failed access to directory". Podobnie jak w przypadku MS one, jeśli jeśli usługa działa na poziomie administratora, musisz uważać, aby nie rozdawać zabezpieczeń tylko ze względu na trochę dodatkowej prędkości.

 0
Author: Bob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-24 12:53:56

Byłbym skłonny zwrócić liczbę w tym przypadku-w zależności od tego, jak zużywasz wyniki, może to być poprawa, Plus zmniejszasz swój parametr footprint o 1/3 i unikasz ciągłego przechodzenia wokół tej listy.

private IEnumerable<FileInfo> GetFileList(string fileSearchPattern, string rootFolderPath)
{
    DirectoryInfo di = new DirectoryInfo(rootFolderPath);

    var fiArr = di.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly);
    foreach (FileInfo fi in fiArr)
    {
        yield return fi;
    }

    var diArr = di.GetDirectories();

    foreach (DirectoryInfo di in diArr)
    {
        var nextRound = GetFileList(fileSearchPattern, di.FullnName);
        foreach (FileInfo fi in nextRound)
        {
            yield return fi;
        }
    }
    yield break;
}

Innym pomysłem byłoby spin off BackgroundWorker obiektów do trollowania przez katalogi. Nie chciałbyś nowego wątku dla każdego katalogu, ale możesz utworzyć go na najwyższym poziomie (najpierw przejść przez GetFileList()), więc jeśli wykonasz na dysku C:\, w przypadku 12 katalogów każdy z tych katalogów będzie przeszukiwany przez inny wątek, który następnie będzie przeszukiwał podkatalogi. Będziesz miał JEDEN wątek przechodzący przez C:\Windows, podczas gdy inny przechodzi przez C:\Program Files. Istnieje wiele zmiennych, jak to ma wpłynąć na wydajność -- trzeba by to przetestować, aby zobaczyć.

 0
Author: Jay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-24 13:39:07

W celu wyszukiwania plików i katalogów chciałbym zaoferować wykorzystanie wielowątkowej biblioteki. NET, która ma szerokie możliwości wyszukiwania. Wszystkie informacje o bibliotece można znaleźć na Githubie: https://github.com/VladPVS/FastSearchLibrary

Jeśli chcesz go pobrać, możesz to zrobić tutaj: https://github.com/VladPVS/FastSearchLibrary/releases

Działa bardzo szybko. Sprawdź sam! Jeśli macie jakieś pytania, proszę je zadać.

Jest to jeden przykładowy sposób użycia:

class Searcher
{
    private static object locker = new object(); 

    private FileSearcher searcher;

    List<FileInfo> files;

    public Searcher()
    {
        files = new List<FileInfo>(); // create list that will contain search result
    }

    public void Startsearch()
    {
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        // create tokenSource to get stop search process possibility

        searcher = new FileSearcher(@"C:\", (f) =>
        {
            return Regex.IsMatch(f.Name, @".*[Dd]ragon.*.jpg$");
        }, tokenSource);  // give tokenSource in constructor


        searcher.FilesFound += (sender, arg) => // subscribe on FilesFound event
        {
            lock (locker) // using a lock is obligatorily
            {
                arg.Files.ForEach((f) =>
                {
                    files.Add(f); // add the next part of the received files to the results list
                    Console.WriteLine($"File location: {f.FullName}, \nCreation.Time: {f.CreationTime}");
                });

                if (files.Count >= 10) // one can choose any stopping condition
                    searcher.StopSearch();
            }
        };

        searcher.SearchCompleted += (sender, arg) => // subscribe on SearchCompleted event
        {
            if (arg.IsCanceled) // check whether StopSearch() called
                Console.WriteLine("Search stopped.");
            else
                Console.WriteLine("Search completed.");

            Console.WriteLine($"Quantity of files: {files.Count}"); // show amount of finding files
        };

        searcher.StartSearchAsync();
        // start search process as an asynchronous operation that doesn't block the called thread
    }
}

To kolejny przykład:

***
List<string> folders = new List<string>
{
  @"C:\Users\Public",
  @"C:\Windows\System32",
  @"D:\Program Files",
  @"D:\Program Files (x86)"
}; // list of search directories

List<string> keywords = new List<string> { "word1", "word2", "word3" }; // list of search keywords

FileSearcherMultiple multipleSearcher = new FileSearcherMultiple(folders, (f) =>
{
  if (f.CreationTime >= new DateTime(2015, 3, 15) &&
     (f.Extension == ".cs" || f.Extension == ".sln"))
    foreach (var keyword in keywords)
      if (f.Name.Contains(keyword))
        return true;
  return false;
}, tokenSource, ExecuteHandlers.InCurrentTask, true); 

***
 0
Author: VladVS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-22 16:36:14