Программа, которая у нас есть на данный момент, выполняет все действия в одном потоке, что может замедлить её работу и привести к тому, что она перестанет отвечать на действия пользователя при запуске задачи, требующей длительного времени.
Однако, если мы запустим такую задачу в отдельном потоке, окно программы будет активно в процессе её выполнения, и пользователь сможет наблюдать за ходом выполнения процесса и даже выполнять другие задачи (если мы дадим ему такие возможности).
Для начала добавим в список using следующие строки:
Итак, мы указываем, что будем использовать пространства имён:
1. System.Threading и System.Threading.Tasks – для управления потоками;
2. System.Timers – для управления таймером. Он требуется для вывода информации на форму.
Полный список функций, предоставляемых этими пространствами имён представлен здесь.
Для начала добавим на форму объект BackgroundWorker я назвал его bgwSchet. Указываем значение свойства WorkerReportProgress = true.
Переходим в меню «События» и создаём 3 события:
1. напротив DoWork пишем bgwSchetDoWork
2. напротив ProgressChanged пишем bgwSchetProgressCanged
3. напротив RunWorkerCompleted пишем bgwSchetRunWorkerCompleted
После этого в файле frmOS7ru.cs будут созданы функции для обработки этих событий.
Теперь добавим в функцию ConsoleKeyEnter(string strKeys) новую команду работы с потоками:
и далее:
Опишем функцию ConsoleKeyEnter_Potok(strKeys):
Затем опишем функцию ConsoleKeyEnter_Potok_Sravnit:
Теперь нам нужно описать действия, выполняемые этим потоком:
Здесь:
Здесь следует отметить, что такие хитрости в большинстве случаев не нужны. Но при отладке приложения нам придётся учитывать возможность возникновения подобных ошибок.
И, конечно же, нам придётся добавить глобальные переменные:
Далее:
Функция Parallel.For позволяет разбить цикл на несколько независимо выполняющихся потоков. Причём в данном случае процесс перейдёт к выполнению следующей команды только после выполнения всех потоков (для этого используется оператор while, который отслеживает, закончили ли потоки свою работу – свойство IsCompleted).
Далее добавим на форму надпись (Label) «Прогресс выполнения операции» и шкалу, которая покажет пользователю, какая часть работы уже выполнена. Для этого используем ProgressBar. Назовём его prgbSchet, чтобы нам было понятно.
Теперь добавим в функцию bgwSchetProgressCanged возможность отслеживать прогресс выполнения:
И, конечно же, опишем, что должно быть выполнено при завершении процесса BackgroundWorker:
Теперь создадим для объекта tmrSchet событие Tick и назовём его tmrSchetTick:
Спрашивается: зачем всё это безобразие? Очень просто: я хочу проверить, что быстрее: выполнить расчёт в одном потоке или разбить на несколько потоков, которые выполняются параллельно.
И ещё: чтобы вывести промежуточные результаты работы потока в окно программы нам придётся приостановить выполнение текущего потока.
И, наконец, опишем функцию Schet:
Теперь перепишем функцию consoleKeyDown:
Как видно из текста, программа будет выдавать сообщение пользователю, когда потоки выполняются: MessageBox.Show("Дождитесь выполнения операции");.
Функция ConsoleAutoCompleteSource тоже меняется, так как мы добавили новые команды. Но с этим вы уже в состоянии справиться самостоятельно.
Скажу только, что добавил команду в список:
и создал списки для новых вариантов команд:
Теперь давайте проверим, как работает эта часть программы(рис. 1):
Итак, мы убедились, что работа с потоками не только упрощает работу пользователя с нашей программой, но и позволяет выполнять длительные расчёты быстрее. Но здесь нужно учитывать возможность пересечения потоков, когда данные из одного потока используются другим. Это может привести к ошибкам в работе программы.
Текст программы можно скачать здесь: .
Жду отзывов и предложений
К списку статей Предыдущий урок
DiamondTigeR
Однако, если мы запустим такую задачу в отдельном потоке, окно программы будет активно в процессе её выполнения, и пользователь сможет наблюдать за ходом выполнения процесса и даже выполнять другие задачи (если мы дадим ему такие возможности).
Для начала добавим в список using следующие строки:
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Threading.Tasks;
using System.Timers;
Итак, мы указываем, что будем использовать пространства имён:
1. System.Threading и System.Threading.Tasks – для управления потоками;
2. System.Timers – для управления таймером. Он требуется для вывода информации на форму.
Полный список функций, предоставляемых этими пространствами имён представлен здесь.
Для начала добавим на форму объект BackgroundWorker я назвал его bgwSchet. Указываем значение свойства WorkerReportProgress = true.
Переходим в меню «События» и создаём 3 события:
1. напротив DoWork пишем bgwSchetDoWork
2. напротив ProgressChanged пишем bgwSchetProgressCanged
3. напротив RunWorkerCompleted пишем bgwSchetRunWorkerCompleted
После этого в файле frmOS7ru.cs будут созданы функции для обработки этих событий.
Теперь добавим в функцию ConsoleKeyEnter(string strKeys) новую команду работы с потоками:
case "поток": ConsoleKeyEnter_Potok(strKeys); break;
и далее:
else if (strKeys == "поток" || strKeys == "_П")
{
arrstrConsoleVvod_Gpr[0] = "поток";
txtbConsole.Text = "Работа с потоками" + NewLine;
}
{
arrstrConsoleVvod_Gpr[0] = "поток";
txtbConsole.Text = "Работа с потоками" + NewLine;
}
Опишем функцию ConsoleKeyEnter_Potok(strKeys):
private void ConsoleKeyEnter_Potok(string strKeys) //Команды работы с потоками
{
if (arrstrConsoleVvod_Gpr[1] == "сравнить")
ConsoleKeyEnter_Potok_Sravnit(strKeys);
else
if (strKeys == "вопрос" || strKeys == "_?")
{
txtbConsole.Text = "Команды работы с данными" + NewLine;
for (int i = 0; i < arrstrVoprosPotok_Gpr.Length; i++) txtbConsole.Text += NewLine + arrstrVoprosPotok_Gpr[i];
}
else if (strKeys == "выход" || strKeys == "_В")
{
txtbConsole.Text = "Введите команду в нижнее поле" + NewLine;
for (int i = 0; i < arrstrConsoleVvod_Gpr[i].Length; i++) arrstrConsoleVvod_Gpr[i] = "";
}
else if (strKeys == "сравнить" || strKeys == "_С")
{
arrstrConsoleVvod_Gpr[1] = "сравнить";
txtbConsole.Text = "Введите число потоков" + NewLine;
}
else if (strKeys == "отмена" || strKeys == "_О")
{
txtbConsole.Text = "Введите команду в нижнее поле" + NewLine;
for (int i = 0; i < arrstrConsoleVvod_Gpr[i].Length; i++) arrstrConsoleVvod_Gpr[i] = "";
}
else
{
txtbConsole.Text += NewLine + "Была набрана команда \"" + strKeys + "\"" + NewLine +
"Эта команда не найдена. Для вывода списка команд набери \"вопрос\"";
}
}
{
if (arrstrConsoleVvod_Gpr[1] == "сравнить")
ConsoleKeyEnter_Potok_Sravnit(strKeys);
else
if (strKeys == "вопрос" || strKeys == "_?")
{
txtbConsole.Text = "Команды работы с данными" + NewLine;
for (int i = 0; i < arrstrVoprosPotok_Gpr.Length; i++) txtbConsole.Text += NewLine + arrstrVoprosPotok_Gpr[i];
}
else if (strKeys == "выход" || strKeys == "_В")
{
txtbConsole.Text = "Введите команду в нижнее поле" + NewLine;
for (int i = 0; i < arrstrConsoleVvod_Gpr[i].Length; i++) arrstrConsoleVvod_Gpr[i] = "";
}
else if (strKeys == "сравнить" || strKeys == "_С")
{
arrstrConsoleVvod_Gpr[1] = "сравнить";
txtbConsole.Text = "Введите число потоков" + NewLine;
}
else if (strKeys == "отмена" || strKeys == "_О")
{
txtbConsole.Text = "Введите команду в нижнее поле" + NewLine;
for (int i = 0; i < arrstrConsoleVvod_Gpr[i].Length; i++) arrstrConsoleVvod_Gpr[i] = "";
}
else
{
txtbConsole.Text += NewLine + "Была набрана команда \"" + strKeys + "\"" + NewLine +
"Эта команда не найдена. Для вывода списка команд набери \"вопрос\"";
}
}
Затем опишем функцию ConsoleKeyEnter_Potok_Sravnit:
private void ConsoleKeyEnter_Potok_Sravnit(string strKeys)//Команды сравнения результатов выполнения потоков
{
if (strKeys == "вопрос" || strKeys == "_?")
{
txtbConsole.Text = "Команды работы с данными" + NewLine;
for (int i = 0; i < arrstrVoprosPotokSravnit_Gpr.Length; i++) txtbConsole.Text += NewLine + arrstrVoprosPotokSravnit_Gpr[i];
}
else if (strKeys == "выход" || strKeys == "_В")
{
txtbConsole.Text = "Введите команду в нижнее поле" + NewLine;
for (int i = 0; i < arrstrConsoleVvod_Gpr[i].Length; i++) arrstrConsoleVvod_Gpr[i] = "";
}
else if (strKeys == "отмена" || strKeys == "_О")
{
txtbConsole.Text = "Введите команду в нижнее поле" + NewLine;
for (int i = 0; i < arrstrConsoleVvod_Gpr[i].Length; i++) arrstrConsoleVvod_Gpr[i] = "";
}
else
{
try
{
if (txtbConsoleMain.Text.Trim().Length > 0)
iChisloPotokov_Gpr = Convert.ToInt16(txtbConsoleMain.Text.Trim());
tmrSchet.Enabled = true;//Активируем таймер
arriVrema_Gpr[0] = arriVrema_Gpr[1] = 0;//Обнуляем счётчики
bgwSchet.RunWorkerAsync();//Запускаем BackgroundWorker
}
catch
{
txtbConsole.Text = NewLine + "Была набрана команда \"" + strKeys + "\"" + NewLine +
"Эта команда не найдена. Для вывода списка команд набери \"вопрос\""+NewLine +
"или введите целое число.";
}
}
}
{
if (strKeys == "вопрос" || strKeys == "_?")
{
txtbConsole.Text = "Команды работы с данными" + NewLine;
for (int i = 0; i < arrstrVoprosPotokSravnit_Gpr.Length; i++) txtbConsole.Text += NewLine + arrstrVoprosPotokSravnit_Gpr[i];
}
else if (strKeys == "выход" || strKeys == "_В")
{
txtbConsole.Text = "Введите команду в нижнее поле" + NewLine;
for (int i = 0; i < arrstrConsoleVvod_Gpr[i].Length; i++) arrstrConsoleVvod_Gpr[i] = "";
}
else if (strKeys == "отмена" || strKeys == "_О")
{
txtbConsole.Text = "Введите команду в нижнее поле" + NewLine;
for (int i = 0; i < arrstrConsoleVvod_Gpr[i].Length; i++) arrstrConsoleVvod_Gpr[i] = "";
}
else
{
try
{
if (txtbConsoleMain.Text.Trim().Length > 0)
iChisloPotokov_Gpr = Convert.ToInt16(txtbConsoleMain.Text.Trim());
tmrSchet.Enabled = true;//Активируем таймер
arriVrema_Gpr[0] = arriVrema_Gpr[1] = 0;//Обнуляем счётчики
bgwSchet.RunWorkerAsync();//Запускаем BackgroundWorker
}
catch
{
txtbConsole.Text = NewLine + "Была набрана команда \"" + strKeys + "\"" + NewLine +
"Эта команда не найдена. Для вывода списка команд набери \"вопрос\""+NewLine +
"или введите целое число.";
}
}
}
Теперь нам нужно описать действия, выполняемые этим потоком:
private void bgwSchetDoWork(object sender, DoWorkEventArgs e)//Основной метод, вызываемый функцией bgwSchetRunWorker
{
ProgressChangedEventArgs PCEA = new ProgressChangedEventArgs(0, "");//Создаём переменную для отслеживания прогресса выполнения процесса
bgwSchetProgressCanged(sender, PCEA);//Вызываем метод ProgressCanged и указываем текущий прогресс выполнения задачи
bSchetFlag_Gpr = true;
tmrSchet.Start();//Запускаем таймер
int n = 1000000;
double[] arrd = new double[iChisloPotokov_Gpr];
for (int i = 0; i < iChisloPotokov_Gpr; i++)
{
arrd[i] = Schet(n, i, arrd[i]);
double dThis=i;
dThis /= iChisloPotokov_Gpr;
iProcent_Gpr = (int)(dThis * 50);
}
PCEA =new ProgressChangedEventArgs(50, "");//Меняем значение переменной для отслеживания прогресса выполнения процесса
bgwSchetProgressCanged(sender, PCEA);//Вызываем метод ProgressCanged и указываем текущий прогресс выполнения задачи
bSchetFlag_Gpr = false;
bSchetFlag2_Gpr = true;
double[] arrd1 = new double[iChisloPotokov_Gpr];
while (!Parallel.For(0, iChisloPotokov_Gpr, delegate(int i)
{
arrd1[i] = Schet(n, i, arrd1[i]);
}).IsCompleted) { };
iProcent_Gpr = 100;
bSchetFlag2_Gpr = false;
}
{
ProgressChangedEventArgs PCEA = new ProgressChangedEventArgs(0, "");//Создаём переменную для отслеживания прогресса выполнения процесса
bgwSchetProgressCanged(sender, PCEA);//Вызываем метод ProgressCanged и указываем текущий прогресс выполнения задачи
bSchetFlag_Gpr = true;
tmrSchet.Start();//Запускаем таймер
int n = 1000000;
double[] arrd = new double[iChisloPotokov_Gpr];
for (int i = 0; i < iChisloPotokov_Gpr; i++)
{
arrd[i] = Schet(n, i, arrd[i]);
double dThis=i;
dThis /= iChisloPotokov_Gpr;
iProcent_Gpr = (int)(dThis * 50);
}
PCEA =new ProgressChangedEventArgs(50, "");//Меняем значение переменной для отслеживания прогресса выполнения процесса
bgwSchetProgressCanged(sender, PCEA);//Вызываем метод ProgressCanged и указываем текущий прогресс выполнения задачи
bSchetFlag_Gpr = false;
bSchetFlag2_Gpr = true;
double[] arrd1 = new double[iChisloPotokov_Gpr];
while (!Parallel.For(0, iChisloPotokov_Gpr, delegate(int i)
{
arrd1[i] = Schet(n, i, arrd1[i]);
}).IsCompleted) { };
iProcent_Gpr = 100;
bSchetFlag2_Gpr = false;
}
Здесь:
for (int i = 0; i < iChisloPotokov_Gpr; i++)
{
arrd[i] = Schet(n, i, arrd[i]);
double dThis=i;
dThis /= iChisloPotokov_Gpr;
iProcent_Gpr = (int)(dThis * 50);
}
- действие, выполняемое в основном потоке BackgroundWorker. Причём здесь я нашёл одну особенность: при попытке написать просто iProcent_Gpr = (int)( (i/iChisloPotokov_Gpr )* 50); мы получаем iProcent_Gpr равное нулю. Поэтому пришлось прибегнуть к хитрости и добавить ещё одну переменную.{
arrd[i] = Schet(n, i, arrd[i]);
double dThis=i;
dThis /= iChisloPotokov_Gpr;
iProcent_Gpr = (int)(dThis * 50);
}
Здесь следует отметить, что такие хитрости в большинстве случаев не нужны. Но при отладке приложения нам придётся учитывать возможность возникновения подобных ошибок.
И, конечно же, нам придётся добавить глобальные переменные:
private int iChisloPotokov_Gpr = 100;//Число потоков
private int[] arriVrema_Gpr = { 0, 0 };//Время выполнения потоков
private bool bSchetFlag_Gpr = false;//выполняются ли потоки
private bool bSchetFlag2_Gpr = false;//выполняется ли 2 часть задачи
private int iProcent_Gpr = 0;//Процент выполнения операции
private int[] arriVrema_Gpr = { 0, 0 };//Время выполнения потоков
private bool bSchetFlag_Gpr = false;//выполняются ли потоки
private bool bSchetFlag2_Gpr = false;//выполняется ли 2 часть задачи
private int iProcent_Gpr = 0;//Процент выполнения операции
Далее:
while (!Parallel.For(0, iChisloPotokov_Gpr, delegate(int i)
{
arrd1[i] = Schet(n, i, arrd1[i]);
}).IsCompleted) { };
{
arrd1[i] = Schet(n, i, arrd1[i]);
}).IsCompleted) { };
Функция Parallel.For позволяет разбить цикл на несколько независимо выполняющихся потоков. Причём в данном случае процесс перейдёт к выполнению следующей команды только после выполнения всех потоков (для этого используется оператор while, который отслеживает, закончили ли потоки свою работу – свойство IsCompleted).
Далее добавим на форму надпись (Label) «Прогресс выполнения операции» и шкалу, которая покажет пользователю, какая часть работы уже выполнена. Для этого используем ProgressBar. Назовём его prgbSchet, чтобы нам было понятно.
Теперь добавим в функцию bgwSchetProgressCanged возможность отслеживать прогресс выполнения:
iProcent_Gpr = e.ProgressPercentage;
И, конечно же, опишем, что должно быть выполнено при завершении процесса BackgroundWorker:
tmrSchet.Stop();//Останавливаем таймер
tmrSchet.Enabled = false;//Отключаем таймер
txtbConsole.Text = "\tПоток 1: " + arriVrema_Gpr[0] + NewLine + "\tПоток 2: " + arriVrema_Gpr[1];
txtbConsole.Text += NewLine + "Введите число потоков" + NewLine;
prgbSchet.Value = iProcent_Gpr;//Указываем, какой процент будет отображаться в поле ProgressBar
tmrSchet.Enabled = false;//Отключаем таймер
txtbConsole.Text = "\tПоток 1: " + arriVrema_Gpr[0] + NewLine + "\tПоток 2: " + arriVrema_Gpr[1];
txtbConsole.Text += NewLine + "Введите число потоков" + NewLine;
prgbSchet.Value = iProcent_Gpr;//Указываем, какой процент будет отображаться в поле ProgressBar
Теперь создадим для объекта tmrSchet событие Tick и назовём его tmrSchetTick:
private void tmrSchetTick(object sender, EventArgs e) //Вызывается при срабатывании счётчика
{
Monitor.Enter(bgwSchet);//Приостанавливает поток bgwSchet
if (bSchetFlag_Gpr)
{
arriVrema_Gpr[0]++;
txtbConsole.Text = "\tПоток 1 выполняется (" + (arriVrema_Gpr[0] * 0.1) + " сек)";
prgbSchet.Value = iProcent_Gpr;
txtbConsole.Invalidate();
prgbSchet.Invalidate();
}
else
{
arriVrema_Gpr[1]++;
txtbConsole.Text = "\tПоток 2 выполняется (" + (arriVrema_Gpr[1]*0.1) + " сек)";
txtbConsole.Invalidate();
prgbSchet.Value = iProcent_Gpr;
prgbSchet.Invalidate();
}
Monitor.Exit(bgwSchet);//Возобновляет поток bgwSchet
}
{
Monitor.Enter(bgwSchet);//Приостанавливает поток bgwSchet
if (bSchetFlag_Gpr)
{
arriVrema_Gpr[0]++;
txtbConsole.Text = "\tПоток 1 выполняется (" + (arriVrema_Gpr[0] * 0.1) + " сек)";
prgbSchet.Value = iProcent_Gpr;
txtbConsole.Invalidate();
prgbSchet.Invalidate();
}
else
{
arriVrema_Gpr[1]++;
txtbConsole.Text = "\tПоток 2 выполняется (" + (arriVrema_Gpr[1]*0.1) + " сек)";
txtbConsole.Invalidate();
prgbSchet.Value = iProcent_Gpr;
prgbSchet.Invalidate();
}
Monitor.Exit(bgwSchet);//Возобновляет поток bgwSchet
}
Спрашивается: зачем всё это безобразие? Очень просто: я хочу проверить, что быстрее: выполнить расчёт в одном потоке или разбить на несколько потоков, которые выполняются параллельно.
И ещё: чтобы вывести промежуточные результаты работы потока в окно программы нам придётся приостановить выполнение текущего потока.
И, наконец, опишем функцию Schet:
private double Schet(int iN, int i,double d) //Функция для рассчёта значения переменной в одном потоке
{
for (int j = 0; j < iN; j++) d += ((j * i )/ iN);
return d;
}
{
for (int j = 0; j < iN; j++) d += ((j * i )/ iN);
return d;
}
Теперь перепишем функцию consoleKeyDown:
private void consoleKeyDown(object sender, KeyEventArgs e) //Обрабатывает нажатие клавиш клавиатуры
{
switch (e.KeyCode)
{
case Keys.Enter: //Условие нажатия клавиши "Enter"
{
if (bSchetFlag_Gpr || bSchetFlag2_Gpr)
{
txtbConsoleMain.Text = "";
MessageBox.Show("Дождитесь выполнения операции");
}
else
{
ConsoleKeyEnter(txtbConsoleMain.Text.Trim());
txtbConsoleMain.Text = "";
ConsoleAutoCompleteSource(arrstrConsoleVvod_Gpr);
}
} break;
case Keys.Space: //Условие нажатия клавиши "Space"
{
if (bSchetFlag_Gpr || bSchetFlag2_Gpr)
{
txtbConsoleMain.Text = "";
MessageBox.Show("Дождитесь выполнения операции");
}
else
{
ConsoleKeyEnter(txtbConsoleMain.Text.Trim());
txtbConsoleMain.Text = "";
ConsoleAutoCompleteSource(arrstrConsoleVvod_Gpr);
}
} break;
default: break;
}
}
{
switch (e.KeyCode)
{
case Keys.Enter: //Условие нажатия клавиши "Enter"
{
if (bSchetFlag_Gpr || bSchetFlag2_Gpr)
{
txtbConsoleMain.Text = "";
MessageBox.Show("Дождитесь выполнения операции");
}
else
{
ConsoleKeyEnter(txtbConsoleMain.Text.Trim());
txtbConsoleMain.Text = "";
ConsoleAutoCompleteSource(arrstrConsoleVvod_Gpr);
}
} break;
case Keys.Space: //Условие нажатия клавиши "Space"
{
if (bSchetFlag_Gpr || bSchetFlag2_Gpr)
{
txtbConsoleMain.Text = "";
MessageBox.Show("Дождитесь выполнения операции");
}
else
{
ConsoleKeyEnter(txtbConsoleMain.Text.Trim());
txtbConsoleMain.Text = "";
ConsoleAutoCompleteSource(arrstrConsoleVvod_Gpr);
}
} break;
default: break;
}
}
Как видно из текста, программа будет выдавать сообщение пользователю, когда потоки выполняются: MessageBox.Show("Дождитесь выполнения операции");.
Функция ConsoleAutoCompleteSource тоже меняется, так как мы добавили новые команды. Но с этим вы уже в состоянии справиться самостоятельно.
Скажу только, что добавил команду в список:
private string[] arrstrVopros_Gpr = { "вопрос", "выход", "консоль", "настройки","поток", "файл" }; //Здесь хранится список команд
и создал списки для новых вариантов команд:
private string[] arrstrVoprosPotok_Gpr = { "вопрос", "выход", "отмена", "сравнить" }; //Здесь хранится список команд для управления потоками
private string[] arrstrVoprosPotokSravnit_Gpr = { "вопрос", "выход", "отмена" }; //Здесь хранится список команд для сравнения результатов работы с потоками
private string[] arrstrVoprosPotokSravnit_Gpr = { "вопрос", "выход", "отмена" }; //Здесь хранится список команд для сравнения результатов работы с потоками
Теперь давайте проверим, как работает эта часть программы(рис. 1):
Рис. 1. Результат работы программы
Итак, мы убедились, что работа с потоками не только упрощает работу пользователя с нашей программой, но и позволяет выполнять длительные расчёты быстрее. Но здесь нужно учитывать возможность пересечения потоков, когда данные из одного потока используются другим. Это может привести к ошибкам в работе программы.
Текст программы можно скачать здесь: .
Жду отзывов и предложений
К списку статей Предыдущий урок
DiamondTigeR
Автор: d.tiger. Дата: 23-10-2012, 20:57
Просмотров: 4073
В избранное:
Уважаемый посетитель, для доступа к ресурсам сайта OS-7.RU, а также для скачивания материалов - Вам необходимо зарегистрироваться либо войти под своим именем.
Комментарии (0)
Распечатать