На ошибках учатся. Помогите, научиться
От: trotski  
Дата: 14.02.06 19:02
Оценка:
Здравствуйте, помогите, пожалуйста, найти красивое решение. Я ищу работу C# стажера, и мне предложили выполнить тестовое задание. Я его с треском провалил. Но дело не в этом, я просто хочу узнать, как такое задание делают нормальные программисты. Хочу научится. Опыта нет, а желание работать есть.
Вот текст задания:
Создать приложение, ищущее текст в файлах, находящихся в указанном каталоге и его подкаталогах. Приложение должно использовать многозадачность. Можно сделать его консольным.
А вот мое решение:
Main.cs
using System;
using System.IO;
using System.Text;

namespace FindText
{

    public class App
    {
        
    
        public static void Main(string[] args)
        {
            //Если программа запущена без аргументов или каталог задан,
            //а строка нет, то информируем о параметрах запуска и выходим            
            if((args.Length < 1)||(args.Length < 3 && args[0] == "-d"))
            {
                PrintInfo();
                return;
            }
            
            //Получаем текущий каталог на случай, если в опциях не будет задан другой
            string dir = Directory.GetCurrentDirectory();

            //Считаем, что каталог не задан, а значит текст для поиска идет с нулевого
            //элемента массива аргументов
            int textBegining = 0;                
            StringBuilder text = new StringBuilder();
            
                        
            //Задан ли каталог?
            if(args[0] == "-d")
            {
                dir = args[1];
                //Задан существующий каталог?
                if(!Directory.Exists(dir))
                {
                    Console.WriteLine("Directory does not exists");
                    return;
                }
                //Текст в массиве аргументов идет со второй позиции
                textBegining = 2;
                        
            }
            
            //Получаем строку для поиска путем слияния всех аргументов,
            //не относящихся к каталогу
            for(int i = textBegining; i < args.Length; i++)
            {
                text.Append(args[i]);
            }
    
            //Вызываем статический метод поиска
            TextFinder.Find(dir, text.ToString());
            Console.WriteLine("OK");
            
            return;
        }
        
        private static void PrintInfo()
        {
                Console.WriteLine("findtext. Test job. Vladimir V Davydov. 2005");
                Console.WriteLine("Usage: findtext [-d directory] \"text to find\"");
        }
    }

}

TextFinder.cs
/*
 * Created by SharpDevelop.
 * User: admin
 * Date: 10.02.2006
 * Time: 17:54
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */

using System;
using System.IO;
using System.Collections;
using System.Threading;

namespace FindText
{
    /// <summary>
    /// Класс, осуществляющий поиск в каталоге.
    /// </summary>
    public class TextFinder
    {
        //Счетчик запущенных потоков, обрабатывающих каталоги
        private static uint _directoryThreadCounter;
        //Счетчик запущенных потоков, обрабатывающих файлы
        private static uint _fileThreadCounter;
        
        //Мьютекс для работы с очередью каталогов
        private static Mutex _dirMutex;
        //Мьютекс для работы с очередью файлов
        private static Mutex _fileMutex;
        //Мьютекс для работы со счетчиком каталогов
        private static Mutex _dirCounterMutex;
        //Мьютекс для работы со счетчиком файлов
        private static Mutex _fileCounterMutex;
        
        //Очередь каталогов для обработки
        private static Queue _dirFIFO;
        //Очередь файлов для обработки
        private static Queue _fileFIFO;
        
        //Текст для поиска
        private static string _text;
        
        
        /// <summary>
        /// Конструктор
        /// </summary>
        /// <remarks>
        /// Конструктор спрятал от греха подальше, 
        /// т.к. будет вызывать только один статический метод
        ///</remarks>
        private TextFinder()
        {
            
        }
        
        ///<summary>Функция потока обработки каталогов</summary>                
        private static void DirProc(object data)
        {
            string currentDir;
            //Пытаемся получить имя каталога из переданного потоку объекта
            try
            {
                currentDir = (string)data;
            }
            catch(InvalidCastException)
            {
                //Производим захват мьютекса для работы со счетчиком потоков
                _dirCounterMutex.WaitOne();
                //Закончили работу и себя за поток уже не считаем ;)
                _directoryThreadCounter--;
                _dirCounterMutex.ReleaseMutex();
                
                return;
            }
            
            
            //Console.WriteLine("Processing directory " + currentDir);
            
            //получаем список подкаталогов в текущем каталоге
            string [] subDirectories = Directory.GetDirectories(currentDir);
            
            //производим захват мьютекса для работы с очередью подкаталогов
            _dirMutex.WaitOne();           
            //добавляем в очередь подкаталоги
            foreach(string dir in subDirectories)
            {
                    _dirFIFO.Enqueue(dir);
            } 
            //Высвобождаем мьютекс
            _dirMutex.ReleaseMutex();
            
            //получаем список файлов в текущем каталоге
            string [] files = Directory.GetFiles(currentDir);
            
            //производим захват мьютекса для работы с очередью файлов
            _fileMutex.WaitOne();
            //Добавляем в очередь обработки файлы
            foreach(string fileName in files)
            {            
                _fileFIFO.Enqueue(fileName);
            }             
            _fileMutex.ReleaseMutex();
            
            //Производим захват мьютекса для работы со счетчиком потоков
            _dirCounterMutex.WaitOne();
            //Закончили работу и себя за поток уже не считаем ;)
            _directoryThreadCounter--;
            _dirCounterMutex.ReleaseMutex();
            return;
        }
        /// <summary>
        /// Функция потока обработки файлов
        /// </summary>
        /// <param name="data"></param>
        private static void FileProc(object data)
        {
            string line;
            string file;
            
            
            //Получаем имя файла из переданного объекта
            try
            {
                 file = (string)data;
            }
            catch(InvalidCastException)
            {
                _fileCounterMutex.WaitOne();
                _fileThreadCounter--;
                _fileCounterMutex.ReleaseMutex();
                return;
            }
            
            
            //Console.WriteLine("Processing file " + file);
            
            #region Сам поиск
            try
            {
                //Если ищем текст, и все файлы рассматриваем как текстовые,
                //то должно быть что-то подобное
                using(StreamReader rs = new StreamReader(file))
                {
                     while ((line = rs.ReadLine()) != null) 
                     {
                         if(line.IndexOf(_text) != -1)
                             Console.WriteLine("Text founded in " + file);
                     }
                    
                }
            }
            catch
            {
                _fileCounterMutex.WaitOne();
                _fileThreadCounter--;
                _fileCounterMutex.ReleaseMutex();
                return;
            }
            #endregion Сам поиск

            //Выходим из потока, а перед выходом уменьшаем кол-во ссылок
            _fileCounterMutex.WaitOne();
            _fileThreadCounter--;
            _fileCounterMutex.ReleaseMutex();
            return;
        }
        
        public static void Find(string directory, string text)
        {
            //Создаем очереди
            _dirFIFO = new Queue();
            _fileFIFO = new Queue();
            
            //Создаем мьютексы
            _dirMutex = new Mutex();
            _fileMutex = new Mutex();
            
            _dirCounterMutex = new Mutex();
            _fileCounterMutex = new Mutex();
            
            _text = text;
            
            //Помещаем в очередь каталогов стартовый
            _dirFIFO.Enqueue(directory);
            
            
            uint dirCount = 0;
            uint fileCount = 0;
            
            string dir = string.Empty;
            string file = string.Empty;
            
            do
            {
                _dirMutex.WaitOne();
                //Смотрим, как там поживает наша очередь
                if(_dirFIFO.Count > 0)
                {
                    //Если в ней есть, что обрабатывать,
                    //то извлекаем элемент
                    dir = (string)_dirFIFO.Dequeue();
                    _dirCounterMutex.WaitOne();                                                
                    ++_directoryThreadCounter;
                    _dirCounterMutex.ReleaseMutex();
                }
                else
                    dir = string.Empty;
                
                _dirMutex.ReleaseMutex();
                
                //Добавляем в пул потоков поток для выбранного каталога
                if(dir != string.Empty)
                    ThreadPool.QueueUserWorkItem(new WaitCallback(DirProc), dir);
                
                
                
                _fileMutex.WaitOne();
                //То же, что и с каталогами, пока очередь не пуста, 
                //обрабатываем очередь, извлекая файлы и добавляя в
                //пул потоков вызов
                if(_fileFIFO.Count > 0)
                {
                    file = (string)_fileFIFO.Dequeue();
                    _fileCounterMutex.WaitOne();                                                
                    ++_fileThreadCounter;
                    _fileCounterMutex.ReleaseMutex();
                }
                else
                    file = string.Empty;
                
                _fileMutex.ReleaseMutex();
                
                if(file != string.Empty)
                    ThreadPool.QueueUserWorkItem(new WaitCallback(FileProc), file);
                
                //Получаем кол-во запущенных потоков, обрабатывающих каталоги
                _dirCounterMutex.WaitOne();                
                dirCount = _directoryThreadCounter;                    
                _dirCounterMutex.ReleaseMutex();                
                
                //Получаем кол-во запущенных потоков, обрабатывающих файлы
                _fileCounterMutex.WaitOne();
                fileCount = _fileThreadCounter;                    
                _fileCounterMutex.ReleaseMutex();
                
                //Заснем (~ 40 мс)
                Thread.Sleep(0);
                
                //Если хоть один поток еще жив, ждем его
            }while(dirCount > 0 || fileCount > 0);
            
            
        }
        
    }
}

Дополнительные "ненужные" счетчики и связанная с ними задержка возникли по причине того, что в .NET Framework 1.1 глюк с заврешением работы приложения: если поток из пула с установленным IsBackground в false завершается позже потока, его в пул поместившего, приложение зависает. Я с этим столкнулся при выполнении задания. Пришлось искусственно порождающий поток останавливать.

Как все это сделать правильней и элегантней? Научите идиота.


14.02.06 22:42: Перенесено модератором из 'О жизни' — Кодт
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.