Dirk Bertels

The greatest malfunction of spirit
is to believe things (Louis Pasteur)

Threads in Managed C++

Last updated 05 October 2011


Introduction

Threads in managed C++ are quite easy to execute. Each thread runs in its own process, so it is often the case that you need to use Delegates to call components that run in their own process, such as Form elements.

This page gives some basic Thread examples that have been proven to work in my implementations. I cover the standard Thread, the BackGroundWorker, and the ThreadPool.

The standard Thread

This just uses the System::Threading::Thread class. I use this basic thread system in network connections where Sockets are used.

using namespace System::Threading;

public: Thread ^ myThread;

System::Void MyClass::StartMyThread()
{
   // MyThreadProcedure() will be called soon as the Thread starts
   myThread = gcnew Thread(gcnew ThreadStart(this,&MyClass::MyThreadProcedure));
   myThread→Start();
   while (!myThread→IsAlive);	// wait for the thread to start
   Thread::Sleep(100);
}


System::Void MyClass::MyThreadProcedure()
{
   // ... Do whatever needs doing in this thread ...
   // myThread→Sleep(timeInMiliseconds)
}


System::Void MyClass::StopMyThread()
{
   if(myThread != nullptr){
      // ... Close / Delete required objects before closing the thread ...
      if(myThread && myThread→IsAlive){
         TimeSpan waitTime = TimeSpan(0, 0, 1);	// 1 second timeout
         if(myThread→Join(waitTime) != true){
            myThread→Abort();	// didnt stop the thread - take more drastic action
         }
      }
   }
}

Using the BackgroundWorker

The BackgroundWorker is a mechanism that uses 3 eventhandlers. One is called to do the actual work, one is called when the work is completed, and one to enable you to take action in the procedure's progress (useful for updating progressbars for example).

I use the BackgroundWorker when a lot of data needs downloading, the BackgroundWorker will download the files using its own process, freeing up the application for the user.

To start the BackgroundWorker, use

bgw→RunWorkerAsync();

To stop the backgroundWorker, use

bgw→CancelAsync();

The following code shows the framework for the BackgroundWorker.

using namespace System::ComponentModel;			// for BackgroundWorker

public: System::ComponentModel::BackgroundWorker ^bgw;

InitializeBackgroundWorker()
{
   bgw = gcnew BackgroundWorker();
   // set required settings
   bgw→WorkerReportsProgress = true;
   bgw→WorkerSupportsCancellation = true;
   // initialise the three eventhandlers
   bgw→DoWork += gcnew DoWorkEventHandler(this, &MyClass::Bgw_DoWork);
   bgw→RunWorkerCompleted += gcnew RunWorkerCompletedEventHandler(this, &MyClass::Bgw_RunWorkerCompleted);
   bgw→ProgressChanged += gcnew ProgressChangedEventHandler(this, &MyClass::Bgw_ProgressChanged);
}


private: void Bgw_DoWork(Object ^sender, DoWorkEventArgs ^e)
{
   // get the backgroundworker that raised this event
   BackgroundWorker^ worker = dynamic_cast<BackgroundWorker^>(sender);
   // assign the result of the operation to the result property of the DoWorkEventArgs
   // this now will be available to the RunWorkerCompleted EventHandler
   e→Result = MyProcedure(worker, e);
}


private: void MyProcedure(BackgroundWorker ^worker, DoWorkEventArgs ^e)
{
   if(worker→CancellationPending){
      e→Cancel = true;
   }
   // ... Do the work here ...
   // ReportProgress(int someNumber) will call Bgw_ProgressChanged()
   worker→ReportProgress(someNumber);	// eg update progressbar
}


private: void Bgw_ProgressChanged(Object^ sender, ProgressChangedEventArgs^ e)
{
   int value = e→ProgressPercentage;
   // ... take action such as updating your progressbar:
   switch (e→ProgressPercentage){
      case 0:
        break;
        ...
   }
}


private: void Bgw_RunWorkerCompleted(Object^ sender, RunWorkerCompletedEventArgs^ e)
{
   // ... Do whatever needs doing once the process is completed ...
}

Using the ThreadPool::QueueUserWorkItem()

There are many uses for the ThreadPool class. One such use is when I want to send an object (service/data) to a server over the network. By using the ThreadPool::QueueUserWorkItem() method you queue the service and data in its own thread and allocate a delegate to handle the task.

Assumimg you have created such an object ready to send, you can start the process by calling ThreadPool::QueueUserWorkItem(). The object is assumed to be derived from a class that implements functionality to enable communication with a server, such as Send(), OnServerResponse(), Connect(), Wait(), etc...

static bool _InProgress = true;
SomeClass ^ obj = gcnew SomeClass();

// optionally set a delegate to handle callback from the server. For example, 
obj->OnServerResponse(gcnew OnServerResponseCallback(this, &MyNamespace::MyClass::HandleServerResponse);

// Queue the service and data.
System::Threading::ThreadPool::QueueUserWorkItem(gcnew WaitCallback(this, &MyNamespace::MyClass::MyProcedure), obj);

// If some kind of 'OnServerClosed' callback is available, handle timeout.
while (true)
{
   DateTime start = DateTime::Now;
   if (_InProgress){	//  
      TimeSpan span = DateTime::Now - start;
      if (span.TotalMinutes > 10){	// 10 minutes
         throw gcnew System::Exception("obj hang for more than 10 minutes");
      }
   }
   else {	// _InProgress set to false by optional OnServerClosed callback
      break;
   }
   Thread::Sleep(3000);
}


Elsewhere in the class you implement the MyProcedure() method, which will connect to the server.

public: void MyProcedure(System::Object ^state)
{
   SomeClass^ obj = dynamic_cast<SomeClass^>(state);
   // assuming this object has a method to connect to a server
   obj->Connect(SomeConfig::RemoteHost, SomeConfig::RemotePort, SomeSocketType::TCP);
   if (!obj->Wait()) { // server error or unavailable
      String^ message = String::Format("Error: {0}", obj->ErrorMessage);
      System::Diagnostics::Debug::WriteLine(message);
   }
}


Comments