C#, .NET Framework: изменение формы из другого потока
На занятиях по системному программированию (мы работаем в системе «Windows 10») писали на языке C# решение из трех проектов в среде Visual Studio Community 2022. Это решение мы взяли из устаревающего методического пакета, оно должно продемонстрировать работу с доменами приложения и потоками. Поскольку домены приложения недоступны на платформе .NET 8.0, вынуждены были использовать платформу .NET Framework 4.8.
Работа готового приложения выглядит вот так:

Главный проект не имеет формы и служит точкой входа в приложение. В нем мы создаем два домена приложения, в рамках каждого из которых загружаем по сборке из двух подчиненных проектов. В каждом из двух подчиненных проектов описано по одной форме: форма с полем для ввода текста и форма-рисователь текста (эти две формы видны на иллюстрации выше).
Поскольку работа в этих двух формах должна выполняться одновременно (в верхней форме пользователь вводит текст, этот же текст одновременно рисуется выбранным шрифтом выбранного цвета в нижней форме), из главного приложения создаем два потока, в каждом из которых запускаем одну из этих двух форм.
Кроме того, при перемещении верхней формы нижняя форма автоматически подстраивается по ширине к верхней форме, а также двигается одновременно с верхней формой. Это реализовано следующим образом: в обработчике события изменения положения верхней формы производится запуск метода нижней формы с передачей данных о ширине и положении. В запущенном методе нижней формы выполняется изменение положения (свойство Location формы) и ширины (свойство Width формы) нижней формы. Поскольку формы запущены в разных потоках, получается, что тут происходит попытка изменения свойств формы из другого потока.
Описание проблемы
Готовое приложение у нас работало через раз. Или раз пять-десять подряд не работает, потом снова работает. И так далее. «Не работает» означает, что при запуске приложения не открывались окна, без выдачи ошибок. Или открывалось только верхнее окно, без нижнего. Когда стали пошагово смотреть в отладчике, выяснилось, что проблема возникает в теле метода нижней формы, в котором пытались из другого потока изменить свойства нижней формы (положение и ширину).
Точнее, выдавалось исключение класса InvalidOperationException с сообщением:
Недопустимая операция в нескольких потоках: попытка доступа к элементу
управления 'Form1' не из того потока, в котором он был создан.
По-английски:
Cross-thread operation not valid. Control accessed from a thread other
than the thread it was created on.
Вот код проблемного метода:
public void FormMove(Point NewLocation, int width)
{
Location = NewLocation;
Width = width;
}
Как видно, код очень простой, тут сложно ошибиться. Но, как оказалось, изменение положения и ширины формы нельзя выполнять таким образом с помощью запуска этого метода из другого потока. Это касается не только положения и ширины формы, а любых свойств элементов управления пользовательского интерфейса. Как известно, сама форма тоже является элементом управления (является потомком класса Control).
Как исправили
Вот исправленный код:
public void FormMove(Point NewLocation, int width)
{
if (InvokeRequired) // вызов из другого потока
{
BeginInvoke((MethodInvoker)delegate {
Location = NewLocation;
Width = width;
});
}
}
То есть мы создали вот такую обертку, используя свойство InvokeRequired формы и метод BeginInvoke формы. Эти свойство и метод есть у всех элементов управления, они наследуются от базового класса Control.
После этого исправления все вышеперечисленные проблемы исчезли.
Конечно, это не единственный способ исправления описанной ошибки. Там их целый ряд. Кроме того, обратите внимание, что на платформе .NET у разных классов есть свойства и методы с названиями InvokeRequired и BeginInvoke, не перепутайте! Также у метода BeginInvoke есть альтернатива в виде метода Invoke.
Если есть желание разобраться в этом подробнее, то рекомендую следующие полезные в этой теме ссылки:
1) Статья (обновлена 25.08.2025 года) «How to handle cross-thread operations with controls» на английском языке из документации на сайте компании Microsoft;
2) Популярное обсуждение «How do I update the GUI from another thread?» на сайте «Stack Overflow» с большим количеством ответов и комментариев;
3) Ответ «Как получить доступ к контролу из другого потока» в списке ответов на самые часто задаваемые вопросы по библиотеке классов «Windows Forms» на «Киберфоруме».