БлогNot. Немного про потоки в C++/CLI

Немного про потоки в C++/CLI

Возможность выполнять программу сразу в нескольких потоках - ценное свойство современных языков программирования. Увы, там есть масса нюансов и если не нужно какое-то особое "ручное" управление потоками, то уместнее всё делать на C#, а не на C++/CLI. На C++/CLI также нужно явное подключение к потоку функций-делегатов, так как анонимных делегатов в нём, в отличие от C#, нет.

1. В первой программке мы решаем следующую задачу - создать в приложении два потока, назначение одного из них - периодическое чтение системного времени и заполнение глобальной структуры DateTime (часы, минуты, секунды), второй поток отвечает за вывод данной структуры в форму приложения. При старте такой программы требуется чуть больше времени, чем обычно (на создание потоков).

Вот основная часть кода, на форме расположена и настроена только метка Label^ label1:

using namespace System::Threading; //подключить в секции namespace
//...

//инициализировать в конструкторе формы
   SetTimeThread = gcnew Thread(gcnew ThreadStart(this, &MyForm::SetTime));
   WriteTimeThread = gcnew Thread(gcnew ThreadStart(this, &MyForm::WriteTime));
   SetTimeThread->IsBackground = true;
   WriteTimeThread->IsBackground = true;
   SetTimeThread->Start();
   WriteTimeThread->Start();
//...

#pragma endregion
  //сервисные функции

  void SetText()  {
   label1->Text = dateTime.ToString("HH:mm:ss");
  }

  void SetTime()  {
   while (true)   {
    dateTime = DateTime::Now;
    Thread::Sleep(100);
   }
  }

  void WriteTime()  {
   try   {
    while (true)    {
     if (this->InvokeRequired)     {
      this->Invoke(gcnew Action(this, &MyForm::SetText));
     }
     else
      SetText();
    }
   }
   catch (...) {}
  }

 private:  System::Threading::Thread^ SetTimeThread; //Поток 1
 private:  System::Threading::Thread^ WriteTimeThread; //Поток 2
 private:  DateTime dateTime;
 private: System::Windows::Forms::Label^ label1;

 Скачать пример 1 - часы на Label c двумя потоками (файл .zip с проектом C++/CLI VS 2019, папка уже создана внутри архива) (6 Кб)

2. Во втором примере мы обходимся с двумя одновременно работающими потоками при помощи Invoke и Action, причём, цикл работы обоих потоков ограничен, а запуска повторных процессов мы избегаем проверкой состояния потока:

 using namespace System::Drawing;
 using namespace System::Threading;
 //...

 Thread ^thisThread, ^thisThread2;

 private: void DoWork(int i) {
  button1->Text = Convert::ToString(i);
 }

 private: System::Void Thread_Start() {
  for (int i = 1; i <= 50; i++) {
   Invoke(gcnew Action<int>(this, &Form1::DoWork), i);
   Thread::Sleep(100);
  }
 }

 private: void DoWork2 (int i) {
  button2->Text = Convert::ToString(i);
 }

 private: System::Void Thread2_Start() {
  for (int i = 1; i <= 50; i++) {
   Invoke(gcnew Action<int>(this, &Form1::DoWork2), i);
   Thread::Sleep(100);
  }
 }

 private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {
  if (!thisThread || !thisThread->IsAlive) {
     thisThread = gcnew Thread(gcnew ThreadStart(this, &Form1::Thread_Start));
     thisThread->Start();
  }
 }

 private: System::Void button2_Click(System::Object^ sender, System::EventArgs^ e) {
  if (!thisThread2 || !thisThread2->IsAlive) {
   thisThread2 = gcnew Thread(gcnew ThreadStart(this, &Form1::Thread2_Start));
   thisThread2->Start();
  }
 }

 private: System::Void Form1_FormClosing(System::Object^  sender, System::Windows::Forms::FormClosingEventArgs^  e) {
  thisThread->Abort();
  thisThread2->Abort();
 }

 Скачать пример 2 - две кнопки, каждая меняет свою надпись в отдельном потоке, файл .zip с проектом C++/CLI VS 2019, папка уже создана внутри архива (10 Кб)

3. Пример имеет чуть более сложный интерфейс и может останавливать (button1) и запускать заново (button2) потоки.

форма приложения 3
форма приложения 3

Один из потоков использует "бесконечный", точнее, прерываемый кнопкой "Стоп" цикл обработки - тот, что выводит текст в нижний TextBox, а в верхнем PictureBox рисуются случайные пиксели самым простым кодом отсюда.

#pragma endregion
  private: System::Threading::Thread ^textThread, ^graphicsThread;
  private: int number;
  private: int sleepTime;
  private: volatile bool threadsState;
  private: Bitmap^ Img;
  private: Graphics^ g;
  
  private: System::Void MyForm_Load(System::Object^ sender, System::EventArgs^ e) {
   Img = gcnew Bitmap (pictureBox1->Size.Width, pictureBox1->Size.Height);
   g = Graphics::FromImage (Img);
   g->Clear(Color::White);
   pictureBox1->Image = Img;
   number = 1; //число в textBox
   sleepTime = 250; //интервал для приостановки потоков, мс
   threadsState = false; //активны ли потоки
  }

  private: System::Void textThreadDelegate() {
   while (threadsState) {
    textBox1->AppendText(number++ + System::Environment::NewLine);
    this->Text = "Id = "+System::Threading::Thread::CurrentThread->ManagedThreadId;
    Application::DoEvents(); //Обязательно в бесконечном цикле
    System::Threading::Thread::Sleep (sleepTime);
   }
  }

  private: System::Void textThreadMethod() {
   if (this->InvokeRequired) this->BeginInvoke(gcnew MethodInvoker(this, &MyForm::textThreadDelegate));
  }
  
  private: System::Void imageThreadDelegate() {
   //while (threadsState) {
    int w = pictureBox1->Image->Width, h = pictureBox1->Image->Height;
    g->Clear(Color::White);
    for (int i = 0; i < w; i++) {
     for (int j = 0; j < h; j++) {
      int rc = 128 + rand() % 128;
      int gc = rand() % std::max(rc, 1);
      int bc = rand() % std::max(gc, 1);
      Img->SetPixel(i, j, Color::FromArgb(255, rc, gc, bc));
     }
    }
    pictureBox1->Refresh();
    this->Text = "Id = " + System::Threading::Thread::CurrentThread->ManagedThreadId;
   //}
  }

  private: System::Void imageThreadMethod() {
   if (this->InvokeRequired) this->BeginInvoke(gcnew MethodInvoker(this, &MyForm::imageThreadDelegate));
  }

  private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {
   threadsState = true;
   textThread = gcnew System::Threading::Thread(gcnew System::Threading::ThreadStart(this, &MyForm::textThreadMethod));
   textThread->IsBackground = true;
   graphicsThread = gcnew System::Threading::Thread(gcnew System::Threading::ThreadStart(this, &MyForm::imageThreadMethod));
   graphicsThread->IsBackground = true;
   textThread->Start();
   graphicsThread->Start();
   button1->Enabled = false; //и отключить кнопку
  }

  private: System::Void button2_Click(System::Object^ sender, System::EventArgs^ e) {
   threadsState = false;
   textThread->Abort();
   graphicsThread->Abort();
   button1->Enabled = true; //и включить кнопку
  }

  private: System::Void MyForm_FormClosing(System::Object^ sender, System::Windows::Forms::FormClosingEventArgs^ e) {
   textThread->Abort();
   graphicsThread->Abort();
  }

Если раскомментировать цикл while (threadsState) в методе imageThreadDelegate - увы, нормально повторяться через интервал будет только один поток из двух.

Двух "бесконечных" циклов в потоках, к тому же, совместно работающих с потоком пользовательского интерфейса, следует, конечно, избегать, тем более, что в .NET есть просто таймеры.

 Проект 3, файл .zip с проектом C++/CLI Visual Studio 2019, папка уже создана внутри архива (7 Кб)


теги: программирование c++/cli

18.11.2020, 00:52; рейтинг: 58