Skip to content

Basics overview part ii: timers, threads and critical sections

jpastuszek edited this page Oct 21, 2010 · 3 revisions

Basics overview part II: Timers, Threads and Critical Sections

1. Timers

NUI provides two ways for working with timers (Please note that the first timer sample code uses nuiTimer while the second one uses nglTimer):

1.1) using a nuiTimer instance

//**************************************
// declare the timer in your class
class MyObject: public MyParent
{
  [...]
private:
  
  bool OnTick(const nuiEvent& rEvent); // timer event receiver
  nuiTimer mTimer;

  double mStartTime;
  nuiEventSink<MyObject> mEventSink;
};

//**************************************
// init the timer in the object constructor, giving the timer period in seconds
MyObject::MyObject() 
: MyParent(), mTimer(0.5f), mEventSink(this)
{
  // connect the timer event to the receiver
  mEventSink.Connect(mTimer.Tick, &MyObject::OnTick);

  // start the timer (could be anywhere, anytime)
  mStartTime = nglTime();
  mTimer.Start();
}

//**************************************
// timer event receiver
bool MyObject::OnTick(const nuiEvent& rEvent)
{
  // a usefull shortcut using the empty constructor of nglTime 
  double currentTime = nglTime();
  
  // my ending condition
  if ((currentTime - mStartTime) > 5.f)
    mTimer.Stop();
  
  // another way to use nglTime
  nglTimeInfo info;
  nglTime date;
  date.GetLocalTime(info);
  
  NGL_OUT(_T("::OnTick currentTime %d/%d/%d %d:%d:%d\n"), 
          info.Year + 1900, info.Month, info.Day, info.Hours, info.Minutes, info.Seconds);

  return true;
}

1.2) Inheriting from nglTimer

class MyTimer : public nglTimer
{
  [...]
  private:

  virtual void OnTick(nglTime Lap);
};

MyTimer::MyTimer(): nglTimer(0.5f)
{
  Start(); // here or somewhere else ...
}

// overloads the OnTick method
void MyTimer::OnTick(nglTime Lap)
{
  // voila!
}

As you may have guessed, nuiTimer inherits from nglTimer using this method, and add the event interface to ease development and prevent having to do too much inheritance in the application code.

2. Threads

2.1) Inheriting from nglThread

class MyClass: public nglThread
{
  [...]
  virtual void OnStart();
  bool mDone;
}

MyClass::MyClass()
: nglThread(_T("MyClass Thread")), mDone(false)
{
}

// virtual callback 
void nglThread::OnStart()
{
  while (!mDone)
  {
    // here is my threaded code

    if (Condition())
      mDone = true;
  }

 // the thread exits
}

[...]
MyClass* myObj = new MyClass();
myObj->Start(); // launch ::OnStart callback
[...]

2.2) Threads synchronisation

MyClass* myObj1 = new MyClass();
MyClass* myObj2 = new MyClass();

// launch the threaded processes
myObj1->Start();
myObj2->Start();
// returns almost immediatly 

// Join makes the process sleeps until the thread ends
myObj1->Join();
// same thing, for the other thread
myObj2->Join(); 

// this way, we are now sure that all the threads have ended, and we can now safely delete everything.
delete myObj1;
delete myObj2;

2.3) Threads communication

We add a message queue to our class. External world can post notifications to this message queue.

#define NOTIF_DONE _T("DONE")

class MyClass: public nglThread
{
  [...]
  // a method to post a message from outside
  void PostMessage(nuiNotification* pNotif);

private:
  // the local message queue
  nuiMessageQueue mQueue;
};

void MyClass::Post(nuiNotification* notif)
{
  mQueue.Post(notif);
}

// virtual callback
void MyClass:OnStart()
{
  nuiNotification* pMessage;

  while (!mDone)
  {
    // wait for a message to come
    pMessage = mQueue.Get();

    // if you need to, you can setup the message queue behavior:
    // pMessage = mQueue.Get(100); // wait for 100ms only and then continue. If the queue was empty, the message is NULL!
    // pMessage = mQueue.Get(0); // don't wait, check if a message is available and returns immediately. The message may be NULL!

    // process message
    if (!pMessage->GetName().Compare(NOTIF_DONE))
      mDone = true;

    delete pMessage;
  }
}

// from another part of the application
[...]
MyClass* myObj = new MyClass();
myObj->Start();
myObj->Post(new nuiNotification(NOTIF_DONE));
myObj->Join();

2.4) Using a nglThreadDelegate

Sometimes you just don’t want to create a whole class for your threaded process.
You are able to delegate the threaded process to an existing method, using the delegates system implemented in nui.

[...]
// declare the thread delegate
nglThreadDelegate delegate(nuiMakeDelegate(this, &MyWidget::MyThreadDelegate));

// and launch the thread process
delegate.Start();
[...]

// here is the delegate method for the threaded process
void MyWidget:: MyThreadDelegate()
{
  while (!mDone)
  {
    [...]
  }
}

3. Critical Sections and Atomics

3.1) locking with critical sections

class MyClass
{
  [...]
  // the c.s. is a shared object for all threads
  nglCriticalSection myCS;
}

void MyClass::MyThreadedMethod()
{
  myCS.Lock(); // will block until the cs is released by the previous "thread-user"
  
  // here is the code you want to protect from multiple parallel accesses
  myAttribute = false;

  myCS.Unlock(); // release the cs 
}

You can also go for a convenient shortcut, using a guard:

void MyClass::MyThreadedMethod()
{
  // handles the lock and release functions
  nglCriticalSectionGuard guard(myCS);  // <=> nglGuard<nglCriticalSection> guard(myCS);
  
  // here is the code you want to protect from multiple parallel accesses
  myAttribute = false;
}

3.2) locking with atomics

If you need to use atomics for an advanced locking system and if you know what you’re doing, you can check the nglAtomic and nglLightLock classes.