Информационый портал Windows 7

У нас вы можете скачать драйвера и программы, найти красивые темы и обои, учебные материалы, а также получить консультации и многое другое.

  • главная
  • контакты
  • карта сайта
 
Программирование » Уроки по C# для начинающих »

Урок 7. Работа с потоками в языке C#.

 
     Программа, которая у нас есть на данный момент, выполняет все действия в одном потоке, что может замедлить её работу и привести к тому, что она перестанет отвечать на действия пользователя при запуске задачи, требующей длительного времени.

     Однако, если мы запустим такую задачу в отдельном потоке, окно программы будет активно в процессе её выполнения, и пользователь сможет наблюдать за ходом выполнения процесса и даже выполнять другие задачи (если мы дадим ему такие возможности).

     Для начала добавим в список using следующие строки:

     using System.Threading;
     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;
     }

     Опишем функцию 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 +
                    "Эта команда не найдена. Для вывода списка команд набери \"вопрос\"";
          }
     }

     Затем опишем функцию 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 +
                    "или введите целое число.";
          }
     }
}

     Теперь нам нужно описать действия, выполняемые этим потоком:

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;
}

     Здесь:
     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 равное нулю. Поэтому пришлось прибегнуть к хитрости и добавить ещё одну переменную.

     Здесь следует отметить, что такие хитрости в большинстве случаев не нужны. Но при отладке приложения нам придётся учитывать возможность возникновения подобных ошибок.

     И, конечно же, нам придётся добавить глобальные переменные:

     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;//Процент выполнения операции

     Далее:

     while (!Parallel.For(0, iChisloPotokov_Gpr, delegate(int i)
     {
          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 событие 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
}

     Спрашивается: зачем всё это безобразие? Очень просто: я хочу проверить, что быстрее: выполнить расчёт в одном потоке или разбить на несколько потоков, которые выполняются параллельно.

     И ещё: чтобы вывести промежуточные результаты работы потока в окно программы нам придётся приостановить выполнение текущего потока.

     И, наконец, опишем функцию Schet:

private double Schet(int iN, int i,double 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;
     }
}

     Как видно из текста, программа будет выдавать сообщение пользователю, когда потоки выполняются: MessageBox.Show("Дождитесь выполнения операции");.

     Функция ConsoleAutoCompleteSource тоже меняется, так как мы добавили новые команды. Но с этим вы уже в состоянии справиться самостоятельно.

     Скажу только, что добавил команду в список:

     private string[] arrstrVopros_Gpr = { "вопрос", "выход", "консоль", "настройки","поток", "файл" }; //Здесь хранится список команд


и создал списки для новых вариантов команд:

     private string[] arrstrVoprosPotok_Gpr = { "вопрос", "выход", "отмена", "сравнить" }; //Здесь хранится список команд для управления потоками
     private string[] arrstrVoprosPotokSravnit_Gpr = { "вопрос", "выход", "отмена" }; //Здесь хранится список команд для сравнения результатов работы с потоками

     Теперь давайте проверим, как работает эта часть программы(рис. 1):

Урок 7. Работа с потоками в языке C#.

Рис. 1. Результат работы программы


     Итак, мы убедились, что работа с потоками не только упрощает работу пользователя с нашей программой, но и позволяет выполнять длительные расчёты быстрее. Но здесь нужно учитывать возможность пересечения потоков, когда данные из одного потока используются другим. Это может привести к ошибкам в работе программы.


     Текст программы можно скачать здесь: Вы не можете скачивать файлы с нашего сервера.




     Жду отзывов и предложений


     К списку статей          Предыдущий урок


     DiamondTigeR

     
Урок 7. Работа с потоками в языке C#.



Автор: d.tiger. Дата: 23-10-2012, 20:57

Просмотров: 4073

В избранное:

Уважаемый посетитель, для доступа к ресурсам сайта OS-7.RU, а также для скачивания материалов - Вам необходимо зарегистрироваться либо войти под своим именем.







 (голосов: 0)
Комментарии (0) Распечатать