}

}

Thread Safety in Windows Forms

One of the common problems faced by Windows programmers is the issue of updating the UI in multithreaded situations. To improve the efficiency of their applications, Windows developers often use threads to perform different tasks in parallel. One thread may be consuming a Web Service, another performing file I/O, another doing some mathematical calculations, and so on. As each thread completes, the developers may want to display the result on the Windows form itself.

However, it is important to know that controls in Windows Forms are bound to a specific thread and are thus not thread safe; this means that if you are updating a control from another thread, you should not call the control's member directly. Figure 10-9 shows the conceptual illustration.

Figure 10-9

To update a Windows Forms control from another thread, use a combination of the following members of that particular control:

□ InvokeRequired property — Returns a Boolean value indicating if the caller must use the Invoke() method when making call to the control if the caller is on a different thread than the control. The InvokeRequired property returns true if the calling thread is not the thread that created the control or if the window handle has not yet been created for that control.

□ Invoke() method — Executes a delegate on the thread that owns the control's underlying windows handle.

□ BeginInvoke() method — Calls the Invoke() method asynchronously.

□ EndInvoke() method — Retrieves the return value of the asynchronous operation started by the BeginInvoke() method.

To see how to use these members, create a Windows application project in Visual Studio 2008. In the default Form1, drag and drop a Label control onto the form and use its default name of Label1. Figure 10-10 shows the control on the form.

Figure 10-10

Double-click the form to switch to its code-behind. The Form1_Load event handler is automatically created for you.

Add the following highlighted code:

private void Form1_Load(object sender, EventArgs e) {

 if (label1.InvokeRequired) {

  MessageBox.Show('Need to use Invoke()');

 } else {

  MessageBox.Show('No need to use Invoke()');

 }

}

This code checks the InvokeRequired property to determine whether you need to call Invoke() if you want to call the Label control's members. Because the code is in the same thread as the Label control, the value for the InvokeRequired property would be false and the message box will print the message No need to use Invoke() .

Now to write some code to display the current time on the Label control and to update the time every second, making it look like a clock. Define the PrintTime() function as follows:

private void PrintTime() {

 try {

  while (true) {

   if (label1.InvokeRequired) {

    label1.Invoke(myDelegate, new object[] {

     label1, DateTime.Now.ToString()

    });

    Thread.Sleep(1000);

   } else label1.Text = DateTime.Now.ToString();

  }

 } catch (Exception ex) {

  Console.WriteLine(ex.Message);

 }

}

Because the PrintTime() function is going to be executed on a separate thread (you will see this later), you need to use the Invoke() method to call a delegate (myDelegate, which you will define shortly) so that the time can be displayed in the Label control. You also insert a delay of one second so that the time is refreshed every second.

Define the updateLabel function so that you can set the Label's control Text property to a specific string:

private void updateLabel(Control ctrl, string str) {

 ctrl.Text = str;

}

This function takes in two parameters — the control to update, and the string to display in the control. Because this function resides in the UI thread, it cannot be called directly from the PrintTime() function; instead, you need to use a delegate to point to it. So the next step is to define a delegate type for this function and then create the delegate:

public partial class Form1 : Form {

 //---delegate type for the updateLabel() function---

 private delegate void delUpdateControl(Control ctrl, string str);

 //--- a delegate---

 private delUpdateControl myDelegate;

Finally, create a thread for the PrintTime() method in the Form1_Load event handler and start it:

private void Form1_Load(object sender, EventArgs e) {

 //...

 //...

 myDelegate = new delUpdateControl(updateLabel);

 Thread t = new Thread(PrintTime);

 t.Start();

}

That's it! When you run the application, the time is displayed and updated every second on the Label control (see Figure 10-11). At the same time, you can move the form, resize it, and so forth, and it is still responsive.

Вы читаете C# 2008 Programmer's Reference
Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

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

Отметить Добавить цитату