public void Test_Multiple_Threads()
        {
            var mlogger = new MultiThreadedCommonLogger();

            var tasks = new List <Task>();

            for (var i = 0; i < 100; ++i)
            {
                var copy = i;
                tasks.Add(Task.Run(() => mlogger.Error(copy.ToString())));
                tasks.Add(Task.Run(() => mlogger.Info(copy.ToString())));
            }

            Task.WaitAll(tasks.ToArray());

            var sbOut = new StringBuilder();
            var sbErr = new StringBuilder();

            mlogger.DumpAllLogs(s => sbOut.AppendLine(s), s => sbErr.AppendLine(s));

            Assert.That(sbOut.ToString(), Contains.Substring("99"), "On completion 99 must be part of the output stream");
            Assert.That(sbErr.ToString(), Contains.Substring("99"), "On completion 99 must be part of the error stream");

            Assert.That(mlogger.HadErrors, Is.True);
        }
        private static void Execute(Options options)
        {
            var directLog   = new ConsoleLogger();
            var threadedLog = new MultiThreadedCommonLogger();

            try
            {
                var io = new IO();

                directLog.Info("Comparing directories");
                directLog.Info("\t[A] {0}", options.DirectoryA);
                directLog.Info("\t[B] {0}", options.DirectoryB);


                var foundComparers = _getComparers(io);
                _dumpComparers(foundComparers, directLog);

                var foundFiles = new FileGatherer(io, options.DirectoryA, options.DirectoryB).CreateFilelist();

                var correlator = new FileToComparerCorrelator(directLog); // Note: not thread safe log, assuming correlator is single threaded

                var matchedFiles = correlator.MatchFilesToComparers(foundComparers, foundFiles);

                directLog.Info("Processing files");

                _processFiles(threadedLog, matchedFiles);

                // Finally always dump remaining logs
                threadedLog.DumpAllLogs(_writeToOutput, _writeToError);

                directLog.Info("Processing files finished");
            }
            catch (Exception ex) when(!System.Diagnostics.Debugger.IsAttached)
            {
                Console.Error.WriteLine(ex);
                Environment.ExitCode = 2;
            }

            // compare errors should not indicate serious (exit code 2)
            if (threadedLog.HadErrors)
            {
                directLog.Warning("There were errors. Examine the output for details!");
                Environment.ExitCode = 1;
            }
            else
            {
                directLog.Info("Completed without errors.");
            }
        }
        /// <summary>
        /// Force a locked flush of all threads to report some progress to the console IO.
        /// Otherwise it will be blank until the entire task queue has been completed
        /// </summary>
        /// <param name="logger"></param>
        private static void _syncLogs(MultiThreadedCommonLogger logger)
        {
            while (_finishEvent.IsSet == false)
            {
                try
                {
                    logger.DumpAllLogs(_writeToOutput, _writeToError);
                }
                catch (Exception e)
                {
                    logger.Exception("Syncing dump failed failed with", e);
                }

                // Since it is supposed to be a separate long running thread, freeze it
                Thread.Sleep(2000);
            }
        }
        /// <summary>
        /// Processes files in an async manner, avoids locking of IO or any other operations when executed in parallel
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="matchedFiles">An lazy evaluated list that performs IO to disk on demand</param>
        private static void _processFiles(MultiThreadedCommonLogger logger, IEnumerable <Tuple <CompareEntry, List <IVFComparer> > > matchedFiles)
        {
            var tasks = new List <Task>();

            _finishEvent = new CountdownEvent(1);


            foreach (var matchedFile in matchedFiles)
            {
                var mf = matchedFile;
                tasks.Add(Task.Run(() => _executeTask(logger, mf.Item1, mf.Item2)).ContinueWith((task) => _checkForErrors(logger, task)));
            }

            // Finally spawn a standalone long running task
            var syncTask = Task.Factory.StartNew(() => _syncLogs(logger), TaskCreationOptions.LongRunning);

            Task.WaitAll(tasks.ToArray());

            // All processing tasks are completed, now signal the sync task to complete
            _finishEvent.Signal();
            syncTask.Wait();
        }