< Intro To Threading & Asynchronous With Examples | Main | C# WinForm Program Example >

 


 

Chapter 3 Part 2:

Threading and the Asynchronous Pattern

 

 

What do we have in this chapter 3 Part 2?

  1. Controlling a Thread

  2. Abort()

  3. Suspend(), Interrupt(), and Resume()

  4. Finishing a Thread

  5. Windows Forms I/O Problem Using Threading

  6. C++ WinForm Program Example

 

 

Controlling a Thread

 

A thread has several operating states that identify its current operating status. Table 3-1 describes the available states. Several Thread class methods - Abort(), Suspend(), Interrupt(), and Resume(), control thread execution inside your program. Each of these methods can change the operating state of your thread.

 

Table 3-1: Thread Operating States

 

State

Description

AbortRequested

Indicates that the Abort() method has been called on the thread.

Running

Indicates that the thread has been started and is running.

Stopped

Indicates that the thread has terminated because the delegate method completed or has been interrupted by the Interrupt() method.

Suspended

Indicates that the thread has stopped processing because of the Suspend() method but is not terminated.

SuspendRequested

Indicates that the Suspend() method has been called on the thread.

Unstarted

Indicates that the thread has been created but not started.

WaitSleepJoin

Indicates that the thread’s delegate method has called either Sleep() or Wait() on a resource or has called Join() to wait on another thread.

 

Abort()

 

The Abort() method is designed to stop a thread from running in a controllable, well-defined manner. Abort() causes a special ThreadAbortException to be immediately raised in the thread delegate method and is the best way to stop a thread dead in its tracks. If your thread delegate does not catch the exception, the thread is automatically stopped and the thread state changes to Stopped. If the delegate catches the exception, the delegate has an opportunity to cancel the abort request by calling the static ResetAbort() method of the Thread class within the delegate catch block. When ResetAbort() is called, the delegate might continue processing outside the originating try/catch block that caught the exception. The following code fragment shows how to catch the ThreadAbortException in a delegate method. We also placed comments that describe how you can cancel the abort operation using the ResetAbort() method.

 

C#

 

public static void MyThreadMethod()

{

    try

    {

        // Do something useful here

    }

    catch (ThreadAbortException e)

    {

        Console.WriteLine("Caught thread abort exception: " + e.Message);

 

        // We could call Thread.ResetAbort here, and the delegate will

        // continue processing after this try - catch block.

    }

}

 

Visual Basic .NET

 

Shared Sub MyThreadMethod()

    Try

        ' Do something useful here

 

    Catch e As ThreadAbortException

        Console.WriteLine("Caught thread abort exception: " + e.Message)

 

        ' We could call Thread.ResetAbort here, and the delegate will

        ' continue processing after this try - catch block.

    End Try

End Sub

 

Suspend(), Interrupt(), and Resume()

 

Another possible way you can stop a thread from running is by calling Suspend() or Interrupt(). Suspend() is different from Abort() because with Suspend(), your thread delegate method only pauses and does not experience an exception. Calling Suspend() will immediately suspend a thread from running and allows you to continue running the thread at a later time by calling Resume().

Calling Interrupt() on a thread will also suspend a thread from running, but only if the delegate routine calls the Monitor.Wait(), Thread.Sleep(), or Thread.Join() method during processing. If the delegate routine never calls one of these methods, the thread will not be interrupted. If your thread does get interrupted and you need to get the thread running again, you’ll have to call the Resume() method.

 

Finishing a Thread

 

When your thread starts running after calling Start(), it runs by default in the foreground, meaning that your main program will wait for your thread to complete before the main program can exit. This wait is great because your main program will not have to make special code to wait on one or more threads to complete, especially if your threads are doing something critical in your application such as saving important data to a file. If you don’t want the main program to wait for your thread to complete, you can change the Thread class property IsBackground from the default Boolean value false to the value true. When the value is true, your thread will run in the background. If the main program exits while your thread is running in the background, the common language runtime will automatically invoke the Abort() method on your thread when the main program exits. If your thread is not doing something critical to your application, running a thread in the background is advisable.

If you need to know when a thread has completed or if you want to wait on a thread to complete, you can call Join(). Join() is designed to wait on a thread until the thread completes, or it can wait for a specified amount of time until the time expires or the thread completes.

 

Windows Forms I/O Problem Using Threading

 

As we have discussed so far in this chapter, we highly recommend using threads to perform blocking operations asynchronously, which enables you to develop applications that provide a much better experience for the user. This recommendation is especially true when working with Windows Forms. Rather than have the form freeze up while you’re waiting on a blocking call, everything in the user’s screen remains nice and responsive.

When working with threads while developing a Windows Forms application, there’s a good chance that you might want to perform I/O on a Windows Form control such as a text box while running a thread delegate method. Doing so presents an I/O problem because the form is designed to process I/O within a Windows Form thread while your delegate method runs in another thread. For a Windows Form to handle I/O correctly, you must allow the Windows Form thread to coordinate (or marshal) the I/O calls. The form processes these calls sequentially, so you should not write directly to the form from another thread.

To allow a Windows Form to coordinate I/O, a special method is available in the forms (System.Windows.Forms.Form) object named Control.Invoke() that’s designed to marshal the I/O calls for a forms thread. Control.Invoke() accepts a delegate method as a parameter and will run your delegate method from within the Windows Forms thread to coordinate I/O. The following programming steps can help you successfully use Invoke() to coordinate I/O from a thread. This code assumes that the delegate methods are defined within your form class, so we can easily reference form objects using the this object.

 

1.      Define a method that will write a string message to your form. The following method is designed to send a message to a text box in a Windows Form:

 

Public Sub MyTextBoxWriter (ByVal MessageToWrite As String)

        Me.StatusBar.Text = MessageToWrite

    End Sub

 

2.      Define a delegate method description for the method in step 1.

 

Public Delegate Sub myDelegateMethod(ByVal Message As String)

 

3.      Declare an instance of your form writer method as a delegate.

 

Dim SB As myDelegateMethod = New myDelegateMethod(AddressOf StatusBarWriter)

 

4.      Call your forms Control.Invoke() method from any thread in your application to marshal the delegate to the forms thread to perform I/O. Because the delegate method is designed to take a string as a parameter, we have to set up an argument array with a string as the first element and pass it to Invoke().

 

Private Sub WriteToStatusBar(ByVal Message As String)

        Dim SB As StatusBarWriterDelegateMethod = New StatusBarWriterDelegateMethod(AddressOf StatusBarWriter)

        SB.Invoke(Message)

    End Sub

 

Additionally, there is an asynchronous version of Invoke() named BeginInvoke() and a results method named EndInvoke() that will allow you to perform Invoke using the .NET Framework asynchronous pattern. The .NET Framework asynchronous pattern design is discussed in the “Asynchronous Pattern” section later in this chapter.

 

C++ WinForm Program Example

 

Create a new Windows Forms application project and you might want to use WinNetworkIOCP for the project and solution names as shown below.

 

C++ WinForm Program Example - Windows form new project creation

 

Drag, drop and rearrange the related controls from the Toolbox window as shown in the following Windows form.

 

C++ WinForm Program Example - WinForm Toolbox components and the design canvas

 

C++ WinForm Program Example - a completed WinForm application

 

Customize the form using the following information.

 

Control

Property

Value

Form1

Text

WinNetworkIOCP

Button

Text

Connect, Send, Close

 

Name

ConnectButton

Label1

Text

IP Address:

TextBox1

Name

IPAddressBox

 

Text

127.0.0.1

Label2

Text

Port:

TextBox2

Name

PortBox

 

Text

5150

Label3

Text

Message to send:

TextBox3

Name

SendMessageBox

 

Text

This is a test message…

 

Multiline

True

 

C++ WinForm Program Example - arranging the WinForm controls and customizing their properties

 

The following Figure shows the almost complete Windows form.

 

C++ WinForm Program Example - WinForm with controls in action

 

Next, we would like to add status bar that can display messages for the actions that happens. Drag and drop a StatusStrip container component at the bottom of the Windows form.

 

C++ WinForm Program Example - adding a StatusStrip component to hold a status bar

 

Then, add a StatusLabel for the StatusStrip. Click the StatusStrip1’s down arrow and select StatusLabel as shown below.

 

C++ WinForm Program Example - setting the StatusStrip to hold a StatusLabel

 

Do some customization for the StatusLabel. Select the statusStrip1 > right click mouse > select Edit Items context menu.

 

C++ WinForm Program Example - Editing the StatusStrip control

 

Select the StatusLabel1 (under the statusStrip1) in the left window (Members:). In the property page on the right, change the Text property’s value to None and Name property’s value to StatusBar. Click OK.

 

C++ WinForm Program Example - Customizing the statusStrip's StatusLabel component

 

The following is a completed Windows form design for this project. Now, we are ready to add codes. You can build and run this project at this stage.

 

C++ WinForm Program Example - a completed WinForm with status bar in action

 

Adding codes for the actions. First of all, double click the top button. This will bring the code page for a button click event. Add the following two lines code to the ConnectButton_Click() method.

 

private: System::Void ConnectButton_Click(System::Object^  sender, System::EventArgs^  e)

{

                 DisableFields();

                 DoNetworkingConnection();

}

 

C++ WinForm Program Example - adding the button click action

 

Don’t forget to add the following using directives.

 

            using namespace System::Net;

            using namespace System::Net::Sockets;

            using namespace System::Threading;

            using namespace System::IO;

 

C++ WinForm Program Example - adding namespaces  

 

Continue adding more codes (DisabledFields() and EnabledFields() methods) just after the ConnectButton_Click() closing bracket. These methods will disable and enable the fields in the form respectively when invoked.

 

            private: void DisableFields()

            {

                        PortBox->Enabled = false;

                        IPAddressBox->Enabled = false;

                        SendMessageBox->Enabled = false;

                        ConnectButton->Enabled = false;

            }

 

            private: void EnableFields()

            {

                        PortBox->Enabled = true;

                        IPAddressBox->Enabled = true;

                        SendMessageBox->Enabled = true;

                        ConnectButton->Enabled = true;

            }

 

C++ WinForm Program Example - adding DisableFields() and EnableFields() methods

 

Add the following code to write messages to the status bar of the Windows form.

 

private: void WriteToStatusBar(String^ Message)

{

          EnableFields();

          StatusBar->Text = Message;

}

 

C++ WinForm Program Example - adding code for the WriteToStatusBar() method

 

Then, add code for the threading, DoNetworkingConnection() method.

 

    private: void DoNetworkingConnection()

    {

            Thread^ MyThread = nullptr;

 

            try

            {

                ThreadStart^ ThreadMethod = gcnew ThreadStart(this, &Form1::ConnectTo);

                MyThread = gcnew Thread(ThreadMethod);

            }

            catch (Exception^ e)

            {

                WriteToStatusBar("Failed to create thread with error: " + e->Message);

                // return 0;

            }

 

            try

            {

                MyThread->Start();

            }

            catch (Exception^ e)

            {

                WriteToStatusBar("The thread failed to start with error: " + e->Message);

            }

}

 

Finally, add the following code for the real network connection setup.

 

    private: void ConnectTo()

    {

           String^ ServerName = this->IPAddressBox->Text;

           int Port = Convert::ToInt32(this->PortBox->Text);

           WriteToStatusBar("IP Address: " + ServerName + "Port: " + Port);

           Socket^ ClientSocket = nullptr;

 

           try

           {

                // Let's connect to a listening server

                try

                {

                    ClientSocket = gcnew Socket(AddressFamily::InterNetwork, SocketType::Stream, ProtocolType::IP);

                    WriteToStatusBar("Socket is OK...");

                }

                catch (Exception^ e)

                {

                    WriteToStatusBar("Failed to create client Socket: " + e->Message);

                }

 

                IPEndPoint^ ServerEndPoint = gcnew IPEndPoint(IPAddress::Parse(ServerName), Convert::ToInt16(Port));

 

                try

                {

                    ClientSocket->Connect(ServerEndPoint);

                    WriteToStatusBar("Connect() is OK...");

                }

                catch (Exception^ e)

                {

                    ClientSocket->Close();

                    WriteToStatusBar("Failed to connect client Socket: " + e->Message);

                }

            }

            catch (Exception^ e)

            {

                WriteToStatusBar(e->Message);

                ClientSocket->Close();

            }

 

            // Let's create a network stream to communicate over the connected Socket.

 

            // This not supposed to be set to nullptr, because if the catch/Finally block

            // invoked, the famous "An unhandled exception of type 'System.NullReferenceException'

            // occurred in executable_file_name" & "Object reference not set to an instance of an object"

            // Exception messages…

            // You may want to change this crap code to avoid 'System.NullReferenceException'

            // by initializing the ClientNetworkStream to something valid instead of nullptr...

            NetworkStream^ ClientNetworkStream = nullptr;

 

            try

            {

                try

                {

                    // Setup a network stream on the client Socket

                    ClientNetworkStream = gcnew NetworkStream(ClientSocket, true);

                    WriteToStatusBar("Instantiating NetworkStream...");

                }

                catch (Exception^ e)

                {

                    // We have to close the client socket here because the network stream did not take ownership of the socket.

                    ClientSocket->Close();

                    WriteToStatusBar("Failed to create a NetworkStream with error: " + e->Message);

                }

 

                StreamWriter^ ClientNetworkStreamWriter = nullptr;

 

                try

                {

                    // Setup a Stream Writer

                    ClientNetworkStreamWriter = gcnew StreamWriter(ClientNetworkStream);

                    WriteToStatusBar("Setting up StreamWriter...");

                }

                catch (Exception^ e)

                {

                    ClientNetworkStream->Close();

                    WriteToStatusBar("Failed to create a StreamWriter with error: " + e->Message);

                }

 

                try

                {

                     ClientNetworkStreamWriter->Write(this->SendMessageBox->Text->ToString());

                     ClientNetworkStreamWriter->Flush();

                     WriteToStatusBar("We wrote " + this->SendMessageBox->Text->Length.ToString() + " character(s) to the server.");

                }

                catch (Exception^ e)

                {

                  WriteToStatusBar("Failed to write to client NetworkStream with error: " + e->Message);

                }

            }

            catch (Exception^ e)

            {

                 WriteToStatusBar("Error lor!!!: " + e->Message);

                // Just exit if there is no receiver...

                // Environment::Exit(0);

            }

            finally

            {

                // Close the network stream once everything is done

                ClientNetworkStream->Close();

            }

    }

 

Before building and running this project, change the [STAThreadAttribute] to [STAThread]. Double click the WinNetworkIOCP.cpp and put the [STAThread] attribute.

 

C++ WinForm program example - changing the [STAThreadAttribute] to [STAThread]

 

Build the project and run it in Debug mode. The following is the sample output. Fill-in a message in the Message to send: text box and click the Connect, Send, Close button.

 

C++ WinForm Program Example - the output is in action

 

The following shows the result of the previous action. Notice the message displayed in the status bar. In this case we don’t have any receiver to receive the message, so the catch block has been reached and the program try to close the non existent client network stream (we initialize the stream to nullptr at the beginning.)

 

C++ Program Example - sending some text to the localhost using default port

 

Let test this program with a receiver. In this case we will use the previously created receiver program, networkstreamsamplecpreceiver.exe. Firstly, run the receiver program with port number 1000. The following is the output screen for the receiver program that waiting a connection from sender.

 

C++ Program Example - running the receiver program and start listening connection

 

Then, run the Windows form program using a localhost and port number 1000. You may want to change the message to send to.

 

C++ WinForm Program Example - running the sender WinForm program

 

The following is the receiver screenshot output sample when the communication was completed successfully.

 

C++ Program Example - The receiver screenshot after the communication was completed

 

A complete added code for this part is shown below.

 

            private: System::Void ConnectButton_Click(System::Object^  sender, System::EventArgs^  e)

           {

                        DisableFields();

                        DoNetworkingConnection();

            }

 

            private: void DisableFields()

            {

                        PortBox->Enabled = false;

                        IPAddressBox->Enabled = false;

                        SendMessageBox->Enabled = false;

                        ConnectButton->Enabled = false;

            }

 

            private: void EnableFields()

            {

                        PortBox->Enabled = true;

                        IPAddressBox->Enabled = true;

                        SendMessageBox->Enabled = true;

                        ConnectButton->Enabled = true;

            }

           

            private: void WriteToStatusBar(String^ Message)

            {

               EnableFields();

               StatusBar->Text = Message;

             }

 

            private: void DoNetworkingConnection()

            {

                  Thread^ MyThread = nullptr;

 

            try

            {

                ThreadStart^ ThreadMethod = gcnew ThreadStart(this, &Form1::ConnectTo);

                MyThread = gcnew Thread(ThreadMethod);

            }

            catch (Exception^ e)

            {

                WriteToStatusBar("Failed to create thread with error: " + e->Message);

                // return 0;

            }

 

            try

            {

                MyThread->Start();

            }

            catch (Exception^ e)

            {

                WriteToStatusBar("The thread failed to start with error: " + e->Message);

            }

    }

 

            private: void ConnectTo()

            {

                        String^ ServerName = this->IPAddressBox->Text;

                        int Port = Convert::ToInt32(this->PortBox->Text);

 

                        WriteToStatusBar("IP Address: " + ServerName + "Port: " + Port);

                        Socket^ ClientSocket = nullptr;

 

            try

            {

                // Let's connect to a listening server

                try

                {

                    ClientSocket = gcnew Socket(AddressFamily::InterNetwork, SocketType::Stream, ProtocolType::IP);

                    WriteToStatusBar("Socket is OK...");

                }

                catch (Exception^ e)

                {

                    WriteToStatusBar("Failed to create client Socket: " + e->Message);

                }

 

                    IPEndPoint^ ServerEndPoint = gcnew IPEndPoint(IPAddress::Parse(ServerName), Convert::ToInt16(Port));

 

                try

                {

                    ClientSocket->Connect(ServerEndPoint);

                    WriteToStatusBar("Connect() is OK...");

                }

                catch (Exception^ e)

                {

                    ClientSocket->Close();

                    WriteToStatusBar("Failed to connect client Socket: " + e->Message);

                }

            }

            catch (Exception^ e)

            {

                WriteToStatusBar(e->Message);

                ClientSocket->Close();

            }

 

            // Let's create a network stream to communicate over the connected Socket.

 

            // This not supposed to be set to nullptr, because if the catch/Finally block

            // invoked, the famous "An unhandled exception of type 'System.NullReferenceException'

            // occurred in executable_file_name" & "Object reference not set to an instance of an object"

            // Exception messages…

            // You may want to change this crap code to avoid 'System.NullReferenceException'

            // by initializing the ClientNetworkStream to something valid instead of nullptr...

            NetworkStream^ ClientNetworkStream = nullptr;

 

            try

            {

                try

                {

                    // Setup a network stream on the client Socket

                    ClientNetworkStream = gcnew NetworkStream(ClientSocket, true);

                    WriteToStatusBar("Instantiating NetworkStream...");

                }

                catch (Exception^ e)

                {

                    // We have to close the client socket here because the network

                    // stream did not take ownership of the socket.

                    ClientSocket->Close();

                    WriteToStatusBar("Failed to create a NetworkStream with error: " + e->Message);

                }

 

                StreamWriter^ ClientNetworkStreamWriter = nullptr;

 

                try

                {

                    // Setup a Stream Writer

                    ClientNetworkStreamWriter = gcnew StreamWriter(ClientNetworkStream);

                    WriteToStatusBar("Setting up StreamWriter...");

                }

                catch (Exception^ e)

                {

                    ClientNetworkStream->Close();

                    WriteToStatusBar("Failed to create a StreamWriter with error: " + e->Message);

                }

 

                try

                {

                    ClientNetworkStreamWriter->Write(this->SendMessageBox->Text->ToString());

                    ClientNetworkStreamWriter->Flush();

                    WriteToStatusBar("We wrote " + this->SendMessageBox->Text->Length.ToString() + " character(s) to the server.");

                }

                catch (Exception^ e)

                {

                    WriteToStatusBar("Failed to write to client NetworkStream with error: " + e->Message);

                }

            }

            catch (Exception^ e)

            {

               WriteToStatusBar("Error lor!!!: " + e->Message);

               // Just exit if there is no receiver...

               // Environment::Exit(0);

            }

            finally

            {

                // Close the network stream once everything is done

                ClientNetworkStream->Close();

            }

    }

 

 

 


 

< Intro To Threading & Asynchronous With Examples | Main | C# WinForm Program Example >