Здравствуйте, помогите, пожалуйста, найти красивое решение. Я ищу работу 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: Перенесено модератором из 'О жизни' — Кодт