Windows Forms






Windows Forms

One more important threading concept relates to user interface development using the System.Windows.Forms namespace. The Microsoft Windows suite of operating systems uses a single-threaded, message-processing-based user interface. This means that only one thread at a time should access the user interface, and any alternate thread interaction should be marshaled via the Windows message pump. The process involves calling a component's InvokeRequired property to determine if marshaling is necessary. Internally, Invoke() will check InvokeRequired anyway, but it can be more efficient to do so beforehand explicitly. Listing 16.10 demonstrates this pattern.

Listing 16.10. Accessing the User Interface via Invoke()

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

class Program : Form
{
      private System.Windows.Forms.ProgressBar _ProgressBar;

    [STAThread]
    static void Main()
    {
          Application.Run(new Program());
    }

    public Program()
    {
        InitializeComponent();
        ThreadStart threadStart = Increment;
        threadStart.BeginInvoke(null, null);
     }
         
     void UpdateProgressBar()
     {
        if (_ProgressBar.InvokeRequired)                         
        {                                                         
            MethodInvoker updateProgressBar = UpdateProgressBar;
            _ProgressBar.Invoke(updateProgressBar);             
        }                                                         
        else                                                     
        {                                                         
            _ProgressBar.Increment(1);                       
        }                                                         
    }

    private void Increment()
    {
        for (int i = 0; i < 100; i++)
        {
            UpdateProgressBar();
            Thread.Sleep(100);
        }
        if (InvokeRequired)                                          
        {                                                           
            // Close cannot be called directly from             
            // a non-UI thread.                                  
            Invoke(new MethodInvoker(Close));                    
        }                                                          
        else                                                       
        {                                                           
            Close();                                            
        }                                                           
    }

    private void InitializeComponent()
    {
            _ProgressBar = new ProgressBar();
            SuspendLayout();

            _ProgressBar.Location = new Point(13, 17);
            _ProgressBar.Size = new Size(267, 19);

            ClientSize = new Size(292, 53);
            Controls.Add(this._ProgressBar);
            Text = "Multithreading in Windows Forms";
            ResumeLayout(false);
    }
 }

This program displays a window that contains a progress bar that automatically starts incrementing. Once the progress bar reaches 100 percent, the dialog box closes.

Notice from Listing 16.10 that you have to check InvokeRequired twice, and then the marshal calls across to the user interface thread if it returns true. In both cases, the marshaling involves instantiating a MethodInvoker delegate that is then passed to Invoke(). Since marshaling across to another thread could be relatively slow, an asynchronous invocation of the call is also available via BeginInvoke() and EndInvoke().

Invoke(), BeginInvoke(), EndInvoke(), and InvokeRequired comprise the members of the System.ComponentModel.ISynchronizeInvoke interface which is implemented by System.Windows.Forms.Control, from which Windows Forms controls derive.

Advanced Topic: Controlling the COM Threading Model with the STAThreadAttribute

With COM, four different apartment-threading models determine the threading rules relating to calls between COM objects. Fortunately, these rulesand the complexity that accompanied themhave disappeared from .NET as long as the program invokes no COM components. The general approach to handling COM Interop is to place all .NET components within the main, single-threaded apartment by decorating a process's Main method with the System.STAThreadAttribute. In so doing, it is not necessary to cross apartment boundaries to invoke the majority of COM components. Furthermore, apartment initialization does not occur, unless a COM Interop call is made.

COM Interop is not necessarily an explicit action by the developer. Microsoft implemented many of the components within the .NET Framework by creating a runtime callable wrapper (RCW) rather than rewriting all the COM functionality within managed code. As a result, COM calls are often made unknowingly. To ensure that these calls are always made from a single-threaded apartment, it is generally a good practice to decorate the main method of all Windows Forms executables with the System.STAThreadAttribute.




 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows