Помогите "ускорить" потоки C#
У меня есть задание:
Разработать многопоточное приложение, выполняющее следующие действия. Для каждого файла в директории построить массив из 256 элементов, содержащий количество значений байт, составляющих файл, т.е. нулевой элемент массива должен содержать количество байт в файле равных 0, первый элемент – кол-во байт равных 1 и т.д.
Соответственно, каждый файл должен обрабатываться отдельным потоком. Результаты вывести (+сохранить в текстовый файл) построчно для каждого обработанного файла, в порядке завершения обработки. Каждая строка должна содержать имя файла, потраченное на обработку время, результирующий массив (элементы через запятую).
В программе должны быть реализованы:
- выбор директории для обработки;
- вывод результатов на экран по мере обработки.
В процессе написания программы я сделал обычный (линейный способ без потоков) для проверки работы в целом, затем сделал потоковый способ.
До этого я ни разу не сталкивался с многопоточным программированием, поэтому получились КОСТЫЛИ.
Есть класс FilesHandler
:
class FilesHandler{
private List<string> PathesOfFiles = new List<string>(); //Содержит пути к файлам
static object locker = new object();
public void HandleEachFile(RichTextBox RTB) //обработчик каждого файла
{
foreach (string path in PathesOfFiles)
{
/*Вариант без потоков*/
//CalculateNew(RTB, path);
/*Вариант с потоками*/
new Thread(() => { CalculateThread(RTB, path); }).Start();
}
}
private void CalculateNew(RichTextBox RTB, string tmpPath)
{
RTB.Text += DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture) + "\t";
Stopwatch stopwatch = new Stopwatch(); //Создаём секундомер
stopwatch.Start(); //Запускаем секундомер
Converter tmpobj = new Converter(); //Создаём объект "файла"
tmpobj.CountBytes(tmpPath); //Метод, который читает файл поблочно и сразу обрабатывает блок(считает байты)
List<string> res = new List<string>(); //создаём массив для результирующей строки
res.Add(GetFileName(tmpPath)); //Записываем первым имя файла
res.Add(" {");
for (int i = 0; i < 256; i++) //Переписываем кол-во каждого байта
{
if (i != 255)
res.Add(tmpobj.GetBytes()[i].ToString() + ", ");
else
res.Add(tmpobj.GetBytes()[i].ToString() + "} ");
}
stopwatch.Stop(); //Останавливаем секундомер, чтобы записать время обработки файла
res.Add(stopwatch.ElapsedMilliseconds.ToString() + " ms");
RTB.Text += String.Join("", res) + '\n'; //RTB - richtextbox как консоль для вывода информации
}
private void CalculateThread(RichTextBox RTB, string tmpPath) //Для варианта с потокам всё вынесено в отдельную функцию
{
lock (locker)
{
Action action2 = () => RTB.Text += DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture) + "\t";
if (RTB.InvokeRequired)
{
RTB.Invoke(action2);
}
else
{
action2();
}
Stopwatch stopwatch = new Stopwatch(); //Создаём секундомер
stopwatch.Start(); //Запускаем секундомер
Converter tmpobj = new Converter(); //Создаём объект "файла"
tmpobj.CountBytes(tmpPath); //Метод, который читает файл поблочно и сразу обрабатывает блок(считает байты)
List<string> res = new List<string>(); //создаём массив для результирующей строки
res.Add(GetFileName(tmpPath)); //Записываем первым имя файла
res.Add(" {");
for (int i = 0; i < 256; i++) //Переписываем кол-во каждого байта
{
if (i != 255)
res.Add(tmpobj.GetBytes()[i].ToString() + ", ");
else
res.Add(tmpobj.GetBytes()[i].ToString() + "} ");
}
stopwatch.Stop(); //Останавливаем секундомер, чтобы записать время обработки файла
res.Add(stopwatch.ElapsedMilliseconds.ToString() + " ms");
Action action3 = () => RTB.Text += String.Join("", res) + '\n';
if (RTB.InvokeRequired)
{
RTB.Invoke(action3);
}
else
{
action3();
}
}
}
}
Также есть класс Converter
, в котором полем является ulong[] Bytes = new ulong[256];
и метод (основной для объяснения), который считает вхождения байтов выбранного файла:
public void CountBytes(string filename)
{
FileStream reader = File.OpenRead(filename);
byte[] buffer = new byte[4096];
int bytesRead = 0;
while ((bytesRead = reader.Read(buffer, 0, buffer.Length)) > 0)
{
for (int i = 0; i < bytesRead; i++)
{
byte b = buffer[i];
Bytes[b]++;
}
}
}
Идея моей реализации такова:
в foreach
при чтении пути каждого файла создается поток, который вызывает метод CalculateThread
.
По сути же у меня каждый поток не имеет доступа к одному и тому же ресурсу, потому что объект Converter
создается внутри метода, но без lock
адекватно работать не будет, потому что на директории в 5гб работает молниеносно, нежели линейный способ, а если взять директорию на 50гб (с таким же кол-вом файлов), то серьезно отстает от линейного, а с lock
+- одинаково, но дольше все равно.
Поток обрабатывает файлик, и потом сразу печатает в richtextbox
результат.
Есть подозрения, что как раз из-за прерывания для записи результата эта задержка возникает.
UPD:
подскажите, пожалуйста, если делать с async await, это так должно выглядеть?
private async void Method(RichTextBox RTB, string path)
{
List<string> res = new List<string>();
await Task.Run(() =>
{
new Thread(() => {
Stopwatch stopwatch = new Stopwatch(); //Создаём секундомер
stopwatch.Start(); //Запускаем секундомер
Converter tmpobj = new Converter(); //Создаём объект "файла"
tmpobj.CountBytes(path); //Метод, который читает файл поблочно и сразу обрабатывает блок(считает байты)
res.Add(String.Concat(GetFileName(path), " {", String.Join(", ", tmpobj.GetBytes()), "}")); // Join сам применяет ToString!
stopwatch.Stop(); //Останавливаем секундомер, чтобы записать время обработки файла
res.Add(stopwatch.ElapsedMilliseconds.ToString() + " ms");
}).Start();
});
RTB.Text += String.Join("", res) + '\n';
}