Threads in windows forms

Threading in Windows Forms

One of the issues which frequently comes up in newsgroups is how to handle threading in a UI. There are two golden rules for Windows Forms:

1) Never invoke any method or property on a control created on another thread other than Invoke , BeginInvoke , EndInvoke or CreateGraphics , and InvokeRequired . Each control is effectively bound to a thread which runs its message pump. If you try to access or change anything in the UI (for example changing the Text property) from a different thread, you run a risk of your program hanging or misbehaving in other ways. You may get away with it in some cases, but only by blind luck. Fortunately, the Invoke , BeginInvoke and EndInvoke methods have been provided so that you can ask the UI thread to call a method for you in a safe manner. 2) Never execute a long-running piece of code in the UI thread. If your code is running in the UI thread, that means no other code is running in that thread. That means you won’t receive events, your controls won’t be repainted, etc. This is a very Bad Thing. You can execute long-running code and periodically call Application.DoEvents() , and this is the natural thing for many VB programmers to wish to do — but I’d advise against it. It means you have to consider re-entrancy issues etc, which I believe are harder to diagnose and fix than «normal» threading problems. You have to judge when to call DoEvents , and you can’t use anything which might block (network access, for instance) without risking an unresponsive UI. I believe there are message pumping issues in terms of COM objects as well, but I don’t have details of them (and I frankly wouldn’t understand them fully anyway).

So, if you have a piece of long-running code which you need to execute, you need to create a new thread (or use a thread pool thread if you prefer) to execute it on, and make sure it doesn’t directly try to update the UI with its results. The thread creation part is the same as any other threading problem, and we’ve addressed that before. The interesting bit is going the other way — invoking a method on the UI thread in order to update the UI.

There are two different ways of invoking a method on the UI thread, one synchronous ( Invoke ) and one asynchronous ( BeginInvoke ). They work in much the same way — you specify a delegate and (optionally) some arguments, and a message goes on the queue for the UI thread to process. If you use Invoke , the current thread will block until the delegate has been executed. If you use BeginInvoke , the call will return immediately. If you need to get the return value of a delegate invoked asynchronously, you can use EndInvoke with the IAsyncResult returned by BeginInvoke to wait until the delegate has completed and fetch the return value.

There are two options when working out how to get information between the various threads involved. The first option is to have state in the class itself, setting it in one thread, retrieving and processing it in the other (updating the display in the UI thread, for example). The second option is to pass the information as parameters in the delegate. Using state somewhere is necessary if you’re creating a new thread rather than using the thread pool — but that doesn’t mean you have to use state to return information to the UI. On the other hand, creating a delegate with lots of parameters often feels clumsy, and is in some ways less efficient than using a simple MethodInvoker or EventHandler delegate. These two delegates are treated in a special (fast) manner by Invoke and BeginInvoke . MethodInvoker is just a delegate which takes no parameters and returns no value (like ThreadStart ), and EventHandler takes two parameters (a sender and an EventArgs parameter and returns no value. Note, however, that if you pass an EventHandler delegate to Invoke or BeginInvoke then even if you specify parameters yourself, they are ignored — when the method is invoked, the sender will be the control you have invoked it with, and the EventArgs will be EventArgs.Empty .

Here is an example which shows several of the above concepts. Notes are provided after the code.

  • State is used to tell the worker thread what number to count up to.
  • A delegate taking a parameter is used to ask the UI to update the status label. The worker thread’s principal method actually just calls UpdateStatus , which uses InvokeRequired to detect whether or not it needs to «change thread». If it does, it then calls BeginInvoke to execute the same method again from the UI thread. This is quite a common way of making a method which interacts with the UI thread-safe. The choice of BeginInvoke rather than Invoke here was just to demonstrate how to invoke a method asynchronously. In real code, you would decide based on whether you needed to block to wait for the access to the UI to complete before continuing or not. In practice, I believe it’s quite rare to actually require UI access to complete first, so I tend to use BeginInvoke instead of Invoke . Another approach might be to have a property which did the appropriate invoking when necessary. That’s easier to use from the client code, but slightly harder work in that you would either have to have another method anyway, or get the MethodInfo for the property setter in order to construct the delegate to invoke. In this case we actually know that BeginInvoke is required because we’re running in the worker thread anyway, but I included the code for the sake of completeness.
  • We don’t call EndInvoke after the BeginInvoke . Unlike all other asynchronous methods (see the later section on the topic) you don’t need to call EndInvoke unless you need the return value of the delegate’s method. Of course, BeginInvoke is also different to all of the other asynchronous methods as it doesn’t cause the delegate to be run on a thread pool thread — that would defeat the whole point in this case!
  • State is used again to tell the UI thread how far we’ve counted so far. We use a MethodInvoker delegate to execute UpdateCount . We call this using Invoke to make sure that it executes on the UI thread. This time there’s no attempt to detect whether or not an Invoke is required. I don’t believe there’s much harm in calling Invoke or BeginInvoke when it’s not required — it’ll just take a little longer than calling the method directly. (If you call BeginInvoke it will have a different effect than calling the method directly as it will occur later, rather than in the current execution flow, of course.) Again, we actually know that we need to call Invoke here anyway.
  • A button is provided to let the user start the thread. It is disabled while the thread is running, and another MethodInvoker delegate is used to enable the button again afterwards.
  • All state which is shared between threads (the current count and the target) is accessed in locks in the way described earlier. We spend as little time as possible in the lock, not updating the UI or anything else while holding the lock. This probably doesn’t make too much difference here, but I believe it’s worth getting into the habit of putting a lock around as little as possible — just as much as is needed. In particular, it would be disastrous to still have the lock in the worker thread when synchronously invoking UpdateCount — the UI thread would then try to acquire the lock as well, and you’d end up with deadlock.
  • The worker thread is set to be a background thread ( IsBackground=true; ) so that when the UI thread exits, the whole application finishes. In other cases where you have a thread which should keep running even after the UI thread has quit, you need to be careful not to call Invoke or BeginInvoke when the UI thread is no longer running — you will either block permanently (waiting for the message to be taken off the queue, with nothing actually looking at messages) or receive an exception.
Читайте также:  Как исправить ошибку 0xc004f074 при активации windows 10

Next page: The Thread Pool and Asynchronous Methods
Previous page: Volatility, Atomicity and Interlocking

How to: Make thread-safe calls to Windows Forms controls

Multithreading can improve the performance of Windows Forms apps, but access to Windows Forms controls isn’t inherently thread-safe. Multithreading can expose your code to very serious and complex bugs. Two or more threads manipulating a control can force the control into an inconsistent state and lead to race conditions, deadlocks, and freezes or hangs. If you implement multithreading in your app, be sure to call cross-thread controls in a thread-safe way. For more information, see Managed threading best practices.

There are two ways to safely call a Windows Forms control from a thread that didn’t create that control. You can use the System.Windows.Forms.Control.Invoke method to call a delegate created in the main thread, which in turn calls the control. Or, you can implement a System.ComponentModel.BackgroundWorker, which uses an event-driven model to separate work done in the background thread from reporting on the results.

Unsafe cross-thread calls

It’s unsafe to call a control directly from a thread that didn’t create it. The following code snippet illustrates an unsafe call to the System.Windows.Forms.TextBox control. The Button1_Click event handler creates a new WriteTextUnsafe thread, which sets the main thread’s TextBox.Text property directly.

The Visual Studio debugger detects these unsafe thread calls by raising an InvalidOperationException with the message, Cross-thread operation not valid. Control «» accessed from a thread other than the thread it was created on. The InvalidOperationException always occurs for unsafe cross-thread calls during Visual Studio debugging, and may occur at app runtime. You should fix the issue, but you can disable the exception by setting the Control.CheckForIllegalCrossThreadCalls property to false .

Читайте также:  Windows internet connection settings

Safe cross-thread calls

The following code examples demonstrate two ways to safely call a Windows Forms control from a thread that didn’t create it:

  1. The System.Windows.Forms.Control.Invoke method, which calls a delegate from the main thread to call the control.
  2. A System.ComponentModel.BackgroundWorker component, which offers an event-driven model.

In both examples, the background thread sleeps for one second to simulate work being done in that thread.

You can build and run these examples as .NET Framework apps from the C# or Visual Basic command line. For more information, see Command-line building with csc.exe or Build from the command line (Visual Basic).

Starting with .NET Core 3.0, you can also build and run the examples as Windows .NET Core apps from a folder that has a .NET Core Windows Forms .csproj project file.

Example: Use the Invoke method with a delegate

The following example demonstrates a pattern for ensuring thread-safe calls to a Windows Forms control. It queries the System.Windows.Forms.Control.InvokeRequired property, which compares the control’s creating thread ID to the calling thread ID. If the thread IDs are the same, it calls the control directly. If the thread IDs are different, it calls the Control.Invoke method with a delegate from the main thread, which makes the actual call to the control.

The SafeCallDelegate enables setting the TextBox control’s Text property. The WriteTextSafe method queries InvokeRequired. If InvokeRequired returns true , WriteTextSafe passes the SafeCallDelegate to the Invoke method to make the actual call to the control. If InvokeRequired returns false , WriteTextSafe sets the TextBox.Text directly. The Button1_Click event handler creates the new thread and runs the WriteTextSafe method.

Example: Use a BackgroundWorker event handler

An easy way to implement multithreading is with the System.ComponentModel.BackgroundWorker component, which uses an event-driven model. The background thread runs the BackgroundWorker.DoWork event, which doesn’t interact with the main thread. The main thread runs the BackgroundWorker.ProgressChanged and BackgroundWorker.RunWorkerCompleted event handlers, which can call the main thread’s controls.

To make a thread-safe call by using BackgroundWorker, create a method in the background thread to do the work, and bind it to the DoWork event. Create another method in the main thread to report the results of the background work, and bind it to the ProgressChanged or RunWorkerCompleted event. To start the background thread, call BackgroundWorker.RunWorkerAsync.

The example uses the RunWorkerCompleted event handler to set the TextBox control’s Text property. For an example using the ProgressChanged event, see BackgroundWorker.

Пошаговое руководство. Осуществление потокобезопасных вызовов элементов управления Windows Forms How to: Make thread-safe calls to Windows Forms controls

Многопоточность может повысить производительность Windows Forms приложений, но доступ к элементам управления Windows Forms не является потокобезопасным. Multithreading can improve the performance of Windows Forms apps, but access to Windows Forms controls isn’t inherently thread-safe. Многопоточность может представлять код для очень серьезных и сложных ошибок. Multithreading can expose your code to very serious and complex bugs. Два или более потока, управляющих элементом управления, могут привести к нестабильному состоянию и вызвать условия гонки, взаимоблокировки, зависания или фиксации. Two or more threads manipulating a control can force the control into an inconsistent state and lead to race conditions, deadlocks, and freezes or hangs. При реализации многопоточности в приложении следует обязательно вызывать элементы управления между потоками потокобезопасным образом. If you implement multithreading in your app, be sure to call cross-thread controls in a thread-safe way. Дополнительные сведения см. в разделе рекомендации по управляемому потоку. For more information, see Managed threading best practices.

Существует два способа безопасного вызова элемента управления Windows Forms из потока, который не был создан этим элементом управления. There are two ways to safely call a Windows Forms control from a thread that didn’t create that control. Метод можно использовать System.Windows.Forms.Control.Invoke для вызова делегата, созданного в основном потоке, который, в свою очередь, вызывает элемент управления. You can use the System.Windows.Forms.Control.Invoke method to call a delegate created in the main thread, which in turn calls the control. Или можно реализовать System.ComponentModel.BackgroundWorker , который использует модель, управляемую событиями, для разделения работы, выполненной в фоновом потоке, от создания отчетов о результатах. Or, you can implement a System.ComponentModel.BackgroundWorker, which uses an event-driven model to separate work done in the background thread from reporting on the results.

Ненадежные вызовы между потоками Unsafe cross-thread calls

Вызвать элемент управления напрямую из потока, который не создал его, неважно. It’s unsafe to call a control directly from a thread that didn’t create it. В следующем фрагменте кода показан незащищенный вызов System.Windows.Forms.TextBox элемента управления. The following code snippet illustrates an unsafe call to the System.Windows.Forms.TextBox control. Button1_Click Обработчик событий создает новый WriteTextUnsafe поток, который устанавливает свойство основного потока TextBox.Text напрямую. The Button1_Click event handler creates a new WriteTextUnsafe thread, which sets the main thread’s TextBox.Text property directly.

Отладчик Visual Studio обнаруживает эти ненадежные вызовы потоков путем вызова исключения InvalidOperationException с сообщением, недопустимой операцией между потоками. Доступ к элементу управления «» осуществляется из потока, отличного от потока, в котором он был создан. The Visual Studio debugger detects these unsafe thread calls by raising an InvalidOperationException with the message, Cross-thread operation not valid. Control «» accessed from a thread other than the thread it was created on. InvalidOperationExceptionВсегда происходит для ненадежных межпотоковых вызовов во время отладки Visual Studio и может возникнуть во время выполнения приложения. The InvalidOperationException always occurs for unsafe cross-thread calls during Visual Studio debugging, and may occur at app runtime. Проблему следует устранить, но можно отключить исключение, задав Control.CheckForIllegalCrossThreadCalls для свойства значение false . You should fix the issue, but you can disable the exception by setting the Control.CheckForIllegalCrossThreadCalls property to false .

Читайте также:  Download kali linux virtual images

Надежные вызовы между потоками Safe cross-thread calls

В следующих примерах кода демонстрируются два способа безопасного вызова элемента управления Windows Forms из потока, который не создал его. The following code examples demonstrate two ways to safely call a Windows Forms control from a thread that didn’t create it:

  1. System.Windows.Forms.Control.InvokeМетод, который вызывает делегат из основного потока для вызова элемента управления. The System.Windows.Forms.Control.Invoke method, which calls a delegate from the main thread to call the control.
  2. System.ComponentModel.BackgroundWorkerКомпонент, который предоставляет модель, управляемую событиями. A System.ComponentModel.BackgroundWorker component, which offers an event-driven model.

В обоих примерах фоновый поток заждет одну секунду для имитации работы, выполняемой в этом потоке. In both examples, the background thread sleeps for one second to simulate work being done in that thread.

Вы можете собрать и запустить эти примеры как .NET Framework приложения из командной строки C# или Visual Basic. You can build and run these examples as .NET Framework apps from the C# or Visual Basic command line. Дополнительные сведения см. в разделе Построение из командной строки с помощью csc.exe или Сборка из командной строки (Visual Basic). For more information, see Command-line building with csc.exe or Build from the command line (Visual Basic).

Начиная с .NET Core 3,0, можно также создавать и запускать примеры как приложения Windows .NET Core из папки с файлом проекта .NET Core Windows Forms . csproj . Starting with .NET Core 3.0, you can also build and run the examples as Windows .NET Core apps from a folder that has a .NET Core Windows Forms .csproj project file.

Пример. использование метода Invoke с делегатом Example: Use the Invoke method with a delegate

В следующем примере показан шаблон для обеспечения потокобезопасных вызовов элемента управления Windows Forms. The following example demonstrates a pattern for ensuring thread-safe calls to a Windows Forms control. Он запрашивает System.Windows.Forms.Control.InvokeRequired свойство, которое СРАВНИВАЕТ идентификатор потока создаваемого элемента управления с идентификатором вызывающего потока. It queries the System.Windows.Forms.Control.InvokeRequired property, which compares the control’s creating thread ID to the calling thread ID. Если идентификаторы потоков совпадают, он вызывает элемент управления напрямую. If the thread IDs are the same, it calls the control directly. Если идентификаторы потоков отличаются, он вызывает Control.Invoke метод с делегатом из основного потока, который выполняет фактический вызов элемента управления. If the thread IDs are different, it calls the Control.Invoke method with a delegate from the main thread, which makes the actual call to the control.

SafeCallDelegate Позволяет задать TextBox свойство элемента управления Text . The SafeCallDelegate enables setting the TextBox control’s Text property. WriteTextSafe Метод запрашивает InvokeRequired . The WriteTextSafe method queries InvokeRequired. Если InvokeRequired возвращает true , WriteTextSafe передает в SafeCallDelegate метод, Invoke чтобы выполнить фактический вызов элемента управления. If InvokeRequired returns true , WriteTextSafe passes the SafeCallDelegate to the Invoke method to make the actual call to the control. Если InvokeRequired возвращает false , WriteTextSafe задает TextBox.Text непосредственно. If InvokeRequired returns false , WriteTextSafe sets the TextBox.Text directly. Button1_Click Обработчик событий создает новый поток и выполняет WriteTextSafe метод. The Button1_Click event handler creates the new thread and runs the WriteTextSafe method.

Пример. использование обработчика событий BackgroundWorker Example: Use a BackgroundWorker event handler

Простой способ реализации многопоточности заключается в использовании System.ComponentModel.BackgroundWorker компонента, использующего модель, управляемую событиями. An easy way to implement multithreading is with the System.ComponentModel.BackgroundWorker component, which uses an event-driven model. Фоновый поток запускает BackgroundWorker.DoWork событие, которое не взаимодействует с основным потоком. The background thread runs the BackgroundWorker.DoWork event, which doesn’t interact with the main thread. Главный поток запускает BackgroundWorker.ProgressChanged BackgroundWorker.RunWorkerCompleted обработчики событий и, которые могут вызывать элементы управления основного потока. The main thread runs the BackgroundWorker.ProgressChanged and BackgroundWorker.RunWorkerCompleted event handlers, which can call the main thread’s controls.

Чтобы сделать потокобезопасный вызов с помощью BackgroundWorker , создайте метод в фоновом потоке, чтобы выполнить работу, и привяжите его к DoWork событию. To make a thread-safe call by using BackgroundWorker, create a method in the background thread to do the work, and bind it to the DoWork event. Создайте другой метод в основном потоке, чтобы сообщить результаты фоновой работы и привязать его к ProgressChanged RunWorkerCompleted событию или. Create another method in the main thread to report the results of the background work, and bind it to the ProgressChanged or RunWorkerCompleted event. Чтобы запустить фоновый поток, вызовите BackgroundWorker.RunWorkerAsync . To start the background thread, call BackgroundWorker.RunWorkerAsync.

В примере с помощью RunWorkerCompleted обработчика событий задается TextBox свойство элемента управления Text . The example uses the RunWorkerCompleted event handler to set the TextBox control’s Text property. Пример использования ProgressChanged события см. в разделе BackgroundWorker . For an example using the ProgressChanged event, see BackgroundWorker.

Оцените статью