Асинхронный доступ к WinForms компонентам

До сих пор мы работали с консолью не просто так. Дело в том, что у потоков есть множество ограничений. Далеко не все объекты .NET также безопасно могут существовать при обращении к ним со стороны нескольких потоков. Давайте посмотрим это на примере. Создайте новое WinForms-приложение и поместите на форму компонент RichTextBox и кнопку. Пишем код, который должен выполняться по нажатию кнопки:

   private void button1_Click(object sender, EventArgs e)
   {
     Thread thread = new Thread(new ThreadStart(ThreadFunction));
     thread.Start();
   }

Здесь у нас создается и запускается на выполнение новый поток, который будет выполнять метод ThreadFunction(). Метод потока выглядит следующим образом:

   void ThreadFunction()
   {
     for (int i = 0; i < 10; i++)
       richTextBox1.AppendText(i.ToString());
   }

Здесь мы в цикле из десяти шагов пытаемся вывести число в компонент RichTextBox. На самом деле у нас не выйдет даже одного раза вывести что-то в этот компонент. Нет, мы сможем откомпилировать пример и даже запустить его на выполнение. Но при попытке нажать на кнопку произойдет исключительная ситуация. На рис. 1 показано сообщение об исключительной ситуации из среды разра- ботки.

Сообщение об исключительной ситуации

Рис. 1. Сообщение об исключительной ситуации

Нельзя обращаться к элементам управления, которые были созданы в других потоках. Наш элемент управления на форме был сделан в основном потоке, а доступ мы пытаемся получить из дочернего потока. Так как же нам получить доступ к компоненту из потока? Эта задача достаточно популярна при работе с потоками и решается довольно легко — через делегаты.

Для вывода текста в RichTextBox напишем следующий метод:

void PrintFunc(string str) { richTextBox1.AppendText(str); }

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

   delegate void PrintInRhichTextBox(string str);
   private PrintInRhichTextBox PrintDelegateFunc;

В конструкторе инициализируем переменную делегата:

   PrintDelegateFunc = new PrintInRhichTextBox(PrintFunc);

Теперь можно вызывать этот метод через делегат. Но не просто так, а через метод Invoke() компонента:

   richTextBox1.Invoke(PrintDelegateFunc,
       new object[] { i.ToString() });

Методу Invoke() передаются два параметра:

  • делегат, который нужно выполнить;
  • массив объектов параметров.

В нашем случае методу делегата нужно передать только один параметр — строку, которая должна выводиться в компоненте, ее и передаем.

Метод Invoke() компонента вызывает указанный метод делегата в потоке, которому и принадлежит этот компонент. Метод Invoke() возвращает значение, которое возвращает вызываемый делегат, или null, если делегат не возвращает никаких значений.

Вот и все. Немного сложновато, но эффективно. При таком вызове делегата он будет работать в том потоке, в котором был создан компонент, а значит, исключительной ситуации не возникнет.



Внимание!!! Если ты копируешь эту статью себе на сайт, то оставляй ссылку непосредственно на эту страницу. Спасибо за понимание

О блоге

Программист, автор нескольких книг серии глазами хакера и просто блогер. Интересуюсь безопасностью, хотя хакером себя не считаю

Обратная связь

Без проблем вступаю в неразборчивые разговоры по e-mail. Стараюсь отвечать на письма всех читателей вне зависимости от страны проживания, вероисповидания на русском или английском языке.

Пишите мне


Я в социальных сетях
Facebook Telegram Youtube Instagram