Rainer's profileCyrons BlogPhotosBlogListsMore Tools Help

Blog


    July 31

    Parallel file search, Part 4

    Hier noch ein paar Unit Tests. Darunter auch ein Concurrency-Test mittels CHESS.

    /**********************************************************************************************
     * In order to make these tests working correctly, they must run in MTA and not STA as usual. *
     * This modification is done in the LocalTestRun.testrunconfig with the                       * 
     * <ExecutionThread apartmentState="MTA" /> -tag.                                             *
     **********************************************************************************************/
    
    using System;
    using System.IO;
    using Cyrons.ParallelSearch;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace ParallelSearchTests
    {
       /// <summary>
       ///This is a test class for ParallelFileSearcher and is intended
       ///to contain all ParallelFileSearcher Unit Tests.
       ///</summary>
       [TestClass]
       public class ParallelFileSearcherTest
       {
          private static int eventCounter;
    
          /// <summary>
          ///Gets or sets the test context which provides
          ///information about and functionality for the current test run.
          ///</summary>
          public TestContext TestContext { get; set; }
          
          /// <summary>
          ///A test for GroupDriveItems
          ///</summary>
          [TestMethod]
          [DeploymentItem("ParallelSearch.dll")]
          public void ShouldCreate3FolderGroups()
          {
             var folders = new List<SearchSet>();
             var searchSet = new SearchSet(
                new DirectoryInfo(@"A:\"), SearchOption.TopDirectoryOnly);
             folders.Add(searchSet);
             searchSet = new SearchSet(
                new DirectoryInfo(@"B:\"), SearchOption.TopDirectoryOnly);
             folders.Add(searchSet);
             searchSet = new SearchSet(
                new DirectoryInfo(@"C:\"), SearchOption.TopDirectoryOnly);
             folders.Add(searchSet);
             /* A fourth entry is for a logical drive that's already in the list.
              * So even we have 4 entries, we should get 3 folder groups only. */
             searchSet = new SearchSet(
                new DirectoryInfo(@"C:\Program files"), SearchOption.TopDirectoryOnly);
             folders.Add(searchSet);
    
             IEnumerable<IGrouping<string, SearchSet>> actual
                = ParallelFileSearcher_Accessor.GroupDriveItems(folders);
             Assert.AreEqual(3, actual.Count());
          }
    
          [TestMethod]
          [HostType("Chess")]
          [TestProperty("ChessBreak", "BeforePreemption")]
          [TestProperty("ChessDebug", "true")]
          [Ignore] // Ran 2000 Threads without problems. Test has been aborted after that to save time.
          public void ChessParallelTest()
          {
             // See the remark at the top of the code.
             Console.WriteLine("Current thread state (should be MTA!): "
                + System.Threading.Thread.CurrentThread.GetApartmentState().ToString());
             const bool ignoreExceptions = true;
             var fileContentSearcher = new ParallelFileSearcher(ignoreExceptions);
             fileContentSearcher.FoundFile += NewFileFoundHandler;
             var searchSets = new List<SearchSet>();
             
             searchSets.Add(new SearchSet(
                new DirectoryInfo(@"C:\Program Files"),
                SearchOption.TopDirectoryOnly));
            
             searchSets.Add(
                new SearchSet(
                new DirectoryInfo(@"H:\VS2008\Lab\CyronsFramework"),
                SearchOption.AllDirectories));
             
             searchSets.Add(
                new SearchSet(
                new DirectoryInfo(@"I:\Developer\VS2008\Projects\Demos\Eigene\CallBackDelegateEssential"),
                SearchOption.AllDirectories));
    
             Console.WriteLine("Bitte warten. Dateien werden gesucht...");
    
             // Define search-parameters.
             const string filenamePattern = "*.cs";
             const string contentToSearchFor = ""; // Could also be null.
    
             // Start the search with those parameters.
             List<FileInfo> fileInfos =
                fileContentSearcher.FindFiles(
                searchSets, filenamePattern, contentToSearchFor, FindMode.FindAll);
             // Do something with fileInfos like, for instance, delete those files.
             Console.WriteLine(fileInfos.Count);
          }
    
          [TestMethod]
          public void ShouldGetCallOutEvent()
          {
             // Siehe Anmerkung.
             Console.WriteLine("Current thread state (should be MTA!): "
                + System.Threading.Thread.CurrentThread.GetApartmentState().ToString());
             // Create a search-list.
             var searchList = new List<SearchSet>();
    
             // Generate Search sets...
             var searchSet = new SearchSet(
                new DirectoryInfo(@"H:\VS2008\Lab\ActiveRecordPattern"),
                SearchOption.AllDirectories);
             // ...and add them to the search-list.
             searchList.Add(searchSet);
    
             searchSet = new SearchSet(
                new DirectoryInfo(@"I:\Developer\VS2008\Projects\Demos\Eigene\CallBackDelegateEssential"),
                SearchOption.AllDirectories);
             searchList.Add(searchSet);
    
             searchSet = new SearchSet(
                new DirectoryInfo(@"C:\Program Files"),
                SearchOption.TopDirectoryOnly);
             searchList.Add(searchSet);
    
             const bool ignoreExceptions = true;
             const string filenamePattern = "*.cs";
             const string contentToSearchFor = null;
             var parallelFileSearcher = new ParallelFileSearcher(ignoreExceptions);
             parallelFileSearcher.CallOutUnauthorizedAccess += ShowCurrentlyDeniedAccess;
             parallelFileSearcher.FoundFile += NewFileFoundHandler;
             List<FileInfo> files = parallelFileSearcher.FindFiles(
                searchList,
                filenamePattern,
                contentToSearchFor,
                FindMode.FindAll);
             Assert.IsTrue(eventCounter > 0);
          }
    
          [TestMethod]
          public void ShouldNotFailOnDeniedAccess()
          {
             var searchList = new List<SearchSet>();
             // The bad guy.
             var searchSet = new SearchSet(
                new DirectoryInfo(@"C:\System Volume Information"), SearchOption.TopDirectoryOnly);
             searchList.Add(searchSet);
             const bool ignoreExceptions = true;
             const string filenamePattern = "*.*";
             const string contentToSearchFor = null;
             var parallelFileSearcher = new ParallelFileSearcher(ignoreExceptions);
             parallelFileSearcher.CallOutUnauthorizedAccess += ShowCurrentlyDeniedAccess;
             parallelFileSearcher.FoundFile += NewFileFoundHandler;
             List<FileInfo> files = parallelFileSearcher.FindFiles(
                searchList,
                filenamePattern,
                contentToSearchFor,
                FindMode.FindAll);
             Assert.IsTrue(eventCounter > 0);
          }
    
          [TestMethod]
          public void ShouldNotFailOnInvalidPath()
          {
             var searchList = new List<SearchSet>();
             // The bad guy.
             var searchSet = new SearchSet(new DirectoryInfo(@"X:\"), SearchOption.AllDirectories);
             searchList.Add(searchSet);
             const bool ignoreExceptions = true;
             const string filenamePattern = "*.cs";
             const string contentToSearchFor = null;
             var parallelFileSearcher = new ParallelFileSearcher(ignoreExceptions);
             parallelFileSearcher.CallOutUnauthorizedAccess += ShowCurrentlyDeniedAccess;
             parallelFileSearcher.FoundFile += NewFileFoundHandler;
             List<FileInfo> files = parallelFileSearcher.FindFiles(
                searchList,
                filenamePattern,
                contentToSearchFor,
                FindMode.FindAll);
             Assert.IsTrue(files.Count == 0);
          }
    
          #region Helpers
    
          private static void ShowCurrentlyDeniedAccess(string fullName)
          {
             Console.WriteLine("Access denied for " + fullName);
             eventCounter++;
          }
    
          private static void NewFileFoundHandler(string fullFileName)
          {
             Console.WriteLine("found " + fullFileName);
          }
    
          #endregion
       }
    }
    

    Parallel file search, Part 3

    Zum Schluß noch ein kleines Demo, das zeigt wie einfach und intuitiv der ParallelFileSearcher zu verwenden ist:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using Cyrons.ParallelSearch;
    
    namespace ParallelSearchDemo
    {
       class Program
       {
          static void Main()
          {
             ShowFindFileContents();
    
             // Verhindert das selbsttätige Schließen des Konsolenfensters.
             Console.WriteLine("\nPress any key to terminate the program.");
             Console.ReadKey();
          }
    
          private static void ShowFindFileContents()
          {
             const bool ignoreExceptions = true;
             var parallelFileSearcher = new ParallelFileSearcher(ignoreExceptions);
             parallelFileSearcher.FoundFile += FileFoundHandler;
             parallelFileSearcher.CallOutUnauthorizedAccess += ShowCurrentlyDeniedAccess;
             
             // Make a container for the search sets.
             List<SearchSet> searchSets = new List<SearchSet>();
    
             // Generate a search set...
             var searchSet = new SearchSet(
                new DirectoryInfo(@"H:\VS2008\Lab\CyronsFramework"),
                SearchOption.AllDirectories);
             // ...and add it to the container.
             searchSets.Add(searchSet);
    
             // Generate another search set...
             searchSet = new SearchSet(
                new DirectoryInfo(@"I:\Developer\VS2008\Projects\Demos\Eigene"),
                SearchOption.AllDirectories);
             // ...and add it to the container.
             searchSets.Add(searchSet);
             
             // Define search-parameters.
             string filenamePattern = "*.cs";
             string contentToSearchFor = ""; // Could also be null.
    
             // Start the search with those parameters.
             Console.WriteLine("Search in progress. Please wait...");
             List<FileInfo> fileInfos =
                parallelFileSearcher.FindFiles(
                searchSets, filenamePattern, contentToSearchFor, FindMode.FindAll);
             // Do something with fileInfos like, for instance, delete those files.
          }
    
          static void ShowCurrentlyDeniedAccess(string fullName)
          {
             Console.WriteLine("Access denied on " + fullName);
          }
    
          static void FileFoundHandler(string fullFileName)
          {
             Console.WriteLine(fullFileName);
          }
       }
    }
    
    /* Sample Output:
    g:\projektinterkosmos\interkosmos\codecontractstrials\codecontractstrials\Program.cs
    g:\projektinterkosmos\interkosmos\codecontractstrials\codecontractstrials\SomeClass.cs
    
    h:\vs2008\lab\decoratorversuch\decoratorversuch\Program.cs
    
    i:\developer\vs2008\projects\demos\eigene\collectiveeventcontract\eineventvieleklassen\CollectiveEventArgs.cs
    i:\developer\vs2008\projects\demos\eigene\collectiveeventcontract\eineventvieleklassen\CollectiveEventContract.cs
    i:\developer\vs2008\projects\demos\eigene\collectiveeventcontract\eineventvieleklassen\Program.cs
    
    Press any key to terminate the program.
    */

    Parallel file search, Part 2

    ParallelFileSearcher-Klasse
    Das Herzstück des Ganzen.
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Security.Permissions;
    using System.Threading;
    
    namespace Cyrons.ParallelSearch
    {
       /// <summary>
       /// Sucht Dateien und/oder nach bestimmtem Inhalt in Dateien.
       /// Es können beliebig viele Suchstrings übergeben werden.
       /// Diese werden nach Laufwerksbuchstabe gruppiert.
       /// Alle Gruppen werden parallel durchsucht.
       /// Somit bietet diese Klasse eine optimale Performance.
       /// </summary>
       public class ParallelFileSearcher
       {
          #region Delegates
    
          /// <summary>
          /// Kapselt eine Methode, die aufgerufen wird, wenn die gesuchte Datei gefunden wurde.
          /// </summary>
          public delegate void FoundFileHandler(string fullFileName);
    
          /// <summary>
          /// Kapselt eine Methode, die aufgerufen wird,
          /// wenn versucht wurde, auf ein Verzeichnis zuzugreifen,
          /// für das keine Zugriffsberechtigung besteht.
          /// </summary>
          public delegate void CurrentAccessDeniedHandler(string fullName);
    
          #endregion
          #region Events
    
          /// <summary>
          /// Tritt ein wenn die gesuchte Datei gefunden wurde.
          /// </summary>
          public event FoundFileHandler FoundFile;
    
          /// <summary>
          /// Tritt ein wenn für den Zugriff auf ein Verzeichnis oder eine Datei keine Berechtigung besteht.
          /// </summary>
          public event CurrentAccessDeniedHandler CallOutUnauthorizedAccess;
    
          #endregion
          #region Fields
    
          private List<string> fileList = new List<string>();
    
          private volatile string content;
          private volatile bool found;
          private FindMode mode;
          private volatile string namePattern;
    
          #endregion Fields
          #region Constructors
    
          /// <summary>
          /// Initialisiert eine neue Instanz der <see cref="ParallelFileSearcher"/> Klasse.
          /// </summary>
          /// <param name="suppressExceptions">Wenn auf <c>true</c> gesetzt,
          /// werden IO-Exceptions ignoriert.</param>
          public ParallelFileSearcher(bool suppressExceptions)
          {
             SuppressExceptions = suppressExceptions;
          }
    
          #endregion Constructors
          #region Properties
    
          /// <summary>
          /// Ruft einen Wert ab, der angibt ob Exceptions ignoriert werden.
          /// </summary>
          /// <value>
          ///     <c>true</c> wenn Exceptions ignoriert werden; anderenfalls <c>false</c>.
          /// </value>
          public bool SuppressExceptions { get; private set; }
    
          #endregion Properties
          #region Methods
          #region Public Methods
          
          /// <summary>
          /// Findet Dateien und/oder bestimmten Inhalt von Dateien.
          /// </summary>
          /// <param name="searchSets">Eine Liste mit Suchsets (Instanzen der 
          /// <see cref="SearchSet"/>-Klasse.</param>
          /// <param name="filenamePattern">Ein Dateinamensmuster (z.B. *.cs).</param>
          /// <param name="contentToSearchFor">Der textuelle Inhalt, nach dem gesucht werden soll.</param>
          /// <param name="findMode">Der durch die 
          /// <see cref="FindMode"/>-Enumeration angegebene Suchmodus.</param>
          /// <returns>Eine Liste mit voll qualifizierten Dateinamen
          ///  (Instanzen der <see cref="FileInfo"/>-Klasse,
          ///  die den Suchkriterien entspricht.</returns>
          public List<FileInfo> FindFiles(
             List<SearchSet> searchSets, string filenamePattern,
             string contentToSearchFor, FindMode findMode)
          {
             var permission = new FileIOPermission(PermissionState.Unrestricted);
             permission.AllFiles = FileIOPermissionAccess.AllAccess;
             SetInstanceParameters(filenamePattern, contentToSearchFor, findMode);
             List<SearchSet> drivesAndFolders = GetDrivesAndFolders(searchSets);
             IEnumerable<IGrouping<string, SearchSet>> fileGroups = GroupDriveItems(drivesAndFolders);
             List<string> internalList = InvokeSearchBase(fileGroups);
             var fileInfos = new List<FileInfo>();
             foreach(var item in internalList)
             {
                fileInfos.Add(new FileInfo(item));
             }
             return fileInfos;
          }
    
          #endregion
          #region Private methods
    
          private static List<SearchSet> GetDrivesAndFolders(
             IEnumerable<SearchSet> searchSets)
          {
             var drivesAndFolders = new List<SearchSet>();
             foreach(var item in searchSets)
             {
                drivesAndFolders.Add(new SearchSet(item.DirectoryInformation, item.Recursive));
             }
             return drivesAndFolders;
          }
    
          private void GetFiles(IEnumerable<FileInfo> files)
          {
             // Wenn keine Content-Angabe vorhanden ist, soll der Inhalt der Files nicht durchsucht werden.
             if(!string.IsNullOrEmpty(content))
             {
                SearchFileContents(files);
             }
             else
             {
                found = true;
                foreach(var file in files)
                {
                   fileList.Add(file.FullName);
                   if(FoundFile != null)
                      FoundFile(file.FullName);
                }
             }
          }
    
          private void SearchFileContents(IEnumerable<FileInfo> files)
          {
             foreach(var file in files)
             {
                StreamReader stream = null;
                try
                {
                   stream = file.OpenText();
                   string fileContent = stream.ReadToEnd();
                   if(fileContent.Contains(content))
                   {
                      found = true;
                      fileList.Add(file.FullName);
                      if(FoundFile != null)
                         FoundFile(file.FullName);
                      if(mode == FindMode.FindOne)
                      {
                         stream.Close();
                         stream.Dispose();
                         break;
                      }
                   }
                }
                catch(IOException)
                {
                   if(!SuppressExceptions)
                      throw;
                }
                catch(UnauthorizedAccessException)
                {
                   if(!SuppressExceptions)
                      throw;
                   if(CallOutUnauthorizedAccess != null)
                      CallOutUnauthorizedAccess(file.FullName);
                }
                finally
                {
                   if(stream != null)
                   {
                      stream.Close();
                      stream.Dispose();
                   }
                }
             }
          }
    
          private void GetFileInfos(SearchSet container, IEnumerable<DirectoryInfo> directories)
          {
             foreach(var directory in directories)
             {
                try
                {
                   FileInfo[] files = directory.GetFiles(namePattern, container.Recursive);
                   if(files.Length > 0)
                      GetFiles(files);
                }
                catch(UnauthorizedAccessException)
                {
                   if(!SuppressExceptions)
                      throw;
                   if(CallOutUnauthorizedAccess != null)
                      CallOutUnauthorizedAccess(directory.FullName);
                }
                catch(IOException)
                {
                   if(!SuppressExceptions)
                      throw;
                }
             }
          }
    
          private static IEnumerable<IGrouping<string, SearchSet>> GroupDriveItems(
             IEnumerable<SearchSet> folders)
          {
             IEnumerable<IGrouping<string, SearchSet>> query =
                folders.GroupBy(drive => drive.DirectoryInformation.Root.ToString(), container => container);
             return query;
          }
    
          private void InvokeSearchByFileGroups(
             IEnumerable<IGrouping<string, SearchSet>> fileGroups,
             AutoResetEvent[] threadReadyEvents)
          {
             int counter = 0;
             foreach(IGrouping<string, SearchSet> fileGroup in fileGroups)
             {
                threadReadyEvents[counter] = new AutoResetEvent(false);
                IEnumerable<SearchSet> containers =
                   from items in fileGroup
                   select items;
                object parameters =
                   new SearchTaskParameters(containers, threadReadyEvents[counter]);
                if(!found)
                   ThreadPool.QueueUserWorkItem(Search, parameters);
                counter++;
             }
          }
    
          private List<string> InvokeSearchBase(
             IEnumerable<IGrouping<string, SearchSet>> fileGroups)
          {
             var threadReadyEvents = new AutoResetEvent[fileGroups.Count()];
             InvokeSearchByFileGroups(fileGroups, threadReadyEvents);
             WaitForThreads(threadReadyEvents);
             return fileList;
          }
    
          private void Search(object parameters)
          {
             if(found && mode == FindMode.FindOne)
                return;
             // Container auspacken -->>
             IEnumerable<SearchSet> folderGroup;
             AutoResetEvent doneEvent;
             UnpackParameterContainer(parameters, out folderGroup, out doneEvent);
             // <<--
             // Schleife für Folder innerhalb eines Laufwerks.
             foreach(SearchSet container in folderGroup)
             {
                var dir = new DirectoryInfo(container.DirectoryInformation.FullName);
                if(!dir.Exists)
                   break;
                dir.GetAccessControl();
                var directories = new DirectoryInfo[] { };
                try
                {
                   directories = dir.GetDirectories();
                }
                catch(UnauthorizedAccessException)
                {
                   if(!SuppressExceptions)
                      throw;
                   if(CallOutUnauthorizedAccess != null)
                      CallOutUnauthorizedAccess(container.DirectoryInformation.FullName);
                }
                GetFileInfos(container, directories);
             }
             doneEvent.Set();
          }
    
          private void SetInstanceParameters(
             string filenamePattern, string contentToSearchFor, FindMode findMode)
          {
             if(string.IsNullOrEmpty(filenamePattern))
                throw new ArgumentException("filenamePattern fehlt.");
             namePattern = filenamePattern;
             content = contentToSearchFor;
             mode = findMode;
          }
    
          private static void UnpackParameterContainer(
             object parameters, out IEnumerable<SearchSet> folderGroup,
             out AutoResetEvent doneEvent)
          {
             folderGroup = ((SearchTaskParameters)parameters).Containers;
             doneEvent = ((SearchTaskParameters)parameters).DoneEvent;
          }
    
          private void WaitForThreads(AutoResetEvent[] threadReadyEvents)
          {
             if(mode == FindMode.FindAll)
                WaitHandle.WaitAll(threadReadyEvents);
             else
                WaitHandle.WaitAny(threadReadyEvents);
          }
    
          #endregion Private methods
          #endregion Methods
       }
    }

    Parallel file search, Part 1

    Parallele Dateisuche? Natürlich macht es keinen Sinn, auf einem Laufwerk verschiedene Ordner gleichzeitig zu durchsuchen. Die Parallelität geht durch die Hardware-Struktur flöten – viel schlimmer noch, die Suche würde verlangsamt, weil der Laufwerks-Controller den Schreib-/Lesekopf unnötig viel hin und her bewegen würde. Diesen Umstand habe ich berücksichtigt. Nur Suchanfragen auf unterschiedlichen logischen Laufwerken werden wirklich parallel durchgeführt (wobei aber darauf zu achten ist, daß eine parallele Suche auf verschiedenen Partitionen einer physikalischen Festplatte ebenfalls keinen Geschwindigkeitsvorteil bringt). Suchstrings für gleiche Laufwerke werden dagegen sequentiell abgearbeitet. Bei der Angabe der Suchkriterien muß der Anwender darauf aber keine Rücksicht nehmen. Diese Gruppierungen werden automatisch unter der Haube vorgenommen.
    Wie funktioniert nun das Ganze?
    Es wird, wie gewohnt nach einer Datei gefragt (die üblichen Wildcards sind natürlich erlaubt) und es wird angegeben wo gesucht werden soll. Es muß aber, anders als bei Standard-Suchprogrammen, möglich sein, mehrere Orte für die Suche anzugeben. Das wird mittels sogenannter SearchSets organisiert. Dazu später mehr.
    Des weiteren kann ich mir zwei verschiedene Szenarien für eine Suche vorstellen.

    1. Man sucht nach einer bestimmten Datei und weiß daß sie nur an einem Ort sein kann, nur an welchem ist nicht klar. Oder man weiß eine Datei an verschiedenen Orten, wobei die Datei aber gleich ist. Nun wäre es in beiden Fällen doch sinnvoll wenn die Suche abgebrochen würde, sobald die Datei gefunden wurde.

    2. Man sucht alle Vorkommen einer bestimmten Datei, um z.B. redundante Dateien zu eliminieren. Hier macht es Sinn daß die Suche bis zum Ende durchläuft und alle Vorkommen zurück gibt.

    Für diese Szenarien gibt es die FindMode-Enumeration. Darüber kann angegeben werden, ob alle Suchtasks abgebrochen werden sollen, sobald einer die gewünschte Datei gefunden hat, oder ob alle Tasks ihre Aufgabe bis zum Ende durchziehen sollen.

    Hier nun die Komponenten von ParallelFileSearch im Einzelnen:

    Die FindMode-Enumeration
    Beschreibung siehe oben.

    namespace Cyrons.ParallelSearch
    {
       /// <summary>
       /// Gibt an ob die Suche nach dem ersten Fund abgebrochen werden soll,
       /// oder ob die Suche fortgeführt werden soll bis alle Pfade durchsucht sind.
       /// </summary>
       public enum FindMode
       {
          /// <summary>
          /// Stoppt die Suche wenn die erste Datei in einem der angegebenen Ordner gefunden wurde,
          /// die dem Suchkriterium entspricht.
          /// </summary>
          FindOne,
          /// <summary>
          /// Findet alle Dateien in allen angegebenen Ordnern, die dem Suchkriterium entsprechen.
          /// </summary>
          FindAll
       }
    }

    SearchSet-Klasse
    Diese Klasse hält die unterschiedlichen Pfadinformationen der Suchanfragen und die Suchoption für jede Anfrage (Rekursiv oder nicht).

    using System.IO;
    
    namespace Cyrons.ParallelSearch
    {
       /// <summary>
       /// Stellt einen Suchset, bestehend aus dem zu durchsuchenden Pfad
       /// und der Angabe, ob dieser Pfad rekursiv (inklusive Unterordnern) durchsucht werden soll.
       /// </summary>
       public class SearchSet
       {
          #region Constructors
    
          /// <summary>
          /// Initialisiert eine neue Instanz der <see cref="SearchSet"/> Klasse.
          /// </summary>
          /// <param name="directoryInfo">Eine Pfadangabe in Form einer 
          /// <see cref="DirectoryInfo"/>-Instanz.</param>
          /// <param name="recursive">Eine <see cref="SearchOption"/>-Enumeration,
          /// die angibt ob der Pfad rekursiv durchsucht werden soll.</param>
          public SearchSet(DirectoryInfo directoryInfo, SearchOption recursive)
          {
             DirectoryInformation = directoryInfo;
             Recursive = recursive;
          }
    
          #endregion Constructors
          #region Properties
    
          /// <summary>
          /// Ruft den aktuell zu durchsuchenden Pfad des Suchsets ab.
          /// </summary>
          /// <value>Eine Instanz der <see cref="DirectoryInfo"/>-Klasse.</value>
          public DirectoryInfo DirectoryInformation { get; private set; }
          
          /// <summary>
          /// Ruft die Suchoption des aktuellen Suchsets ab.
          /// </summary>
          /// <value>Eine der Enumerationen von <see cref="SearchOption"/>.</value>
          public SearchOption Recursive { get; private set; }
    
          #endregion Properties
       }
    }
     

    SearchTaskParameters-Klasse
    ParallelFileSearch arbeitet mit dem ThreadPool. QueueUserWorkItem erwartet neben dem WaitCallback-Delegaten einen Parameter vom Typ Objekt. Da ein Suchtask aber mit mehreren Parametern gefüttert werden muß, werden diese über die SearchTaskParameters-Klasse definiert und zusammengefasst. Innerhalb eines Threads werden diese dann wieder ausgepackt. Die SearchTaskParameters-Klasse ist internal deklariert, weil sie ausserhalb des APIs nicht sichtbar sein muß.

    using System.Collections.Generic;
    using System.Threading;
    
    namespace Cyrons.ParallelSearch
    {
       /// <summary>
       /// Beinhaltet Parameter, die an die Suchthreads übegeben werden.
       /// <remarks>
       /// Eine Methode die über den Threadpool gestartet wird,
       /// darf nur einen Parameter vom Typ Object haben.
       /// Mittels der ParameterContainer-Klasse wird diese Klippe elegant umschifft.
       /// </remarks>
       /// </summary>
       internal class SearchTaskParameters
       {
          #region Constructors
    
          /// <summary>
          /// Initialisiert eine neue Instanz der <see cref="SearchTaskParameters"/>-Klasse.
          /// </summary>
          /// <param name="containers">SearchSets</param>
          /// <param name="doneEvent">Jeder Thread bekommt ein 
          /// <see cref="AutoResetEvent"/> in's Gepäck.</param>
          internal SearchTaskParameters(
             IEnumerable<SearchSet> containers, AutoResetEvent doneEvent)
          {
             Containers = containers;
             DoneEvent = doneEvent;
          }
    
          #endregion Constructors
          #region Properties
    
          internal AutoResetEvent DoneEvent { get; private set; }
          internal IEnumerable<SearchSet> Containers { get; private set; }
    
          #endregion Properties
       }
    }
    July 24

    Directory.GetFiles und System Volume Information, der ewige Krieg

    Schreibt man eine Suchfunktion, die rekursiv ein NTFS-Laufwerk durchsuchen soll, kommt es zu einer UnauthorizedAccessException wenn der Algorithmus bei System Volume Information ankommt. So kann man das Problem lösen:

    using System;
    using System.Collections.Generic;
    using System.IO;
    
    namespace UnauthorizedAccessExceptionWorkAround
    {
       class Program
       {
          static void Main()
          {
             var someList = new List<string>();
             string filenamePattern = "*.txt";         
             string[] directories = Directory.GetDirectories("c:\\");
             Console.WriteLine("Searching, please wait..." + Environment.NewLine);
             foreach(string directory in directories)
             {
                try
                {               
                   someList.AddRange(Directory.GetFiles(
                                        directory, filenamePattern, SearchOption.AllDirectories));
                }
                catch(UnauthorizedAccessException)
                {
                   Console.WriteLine("Skipped directory {0}", directory);
                }
             }
    
             Console.WriteLine("=======================================================");
    
             if(someList.Count > 0)
             {
                Console.WriteLine(filenamePattern + " found at:");
                foreach(string item in someList)
                {
                   Console.WriteLine(item);
                }
             }
    
             // Verhindert das selbsttätige Schließen des Konsolenfensters.
             Console.WriteLine("\nPress any key to terminate the program.");
             Console.ReadKey();
          }
       }
    }

    Im Catch-Block könnte z.B. auch ein Event gefeuert, daß eine darüber liegende Applikation über den Vorfall informiert.
    July 14

    Musik kennt keine Grenzen

    Mal nichts über Software-Entwicklung, dafür aber mindestens genau so wichtig!

    http://vimeo.com/moogaloop.swf?clip_id=2539741