BestForPlatformNoLock
as this will automatically use the most appropriate timer queue implementation and it will dispatch timers in such a way that there is no risk of deadlock in your code when actioning timers and setting timers.
void CSocketServer::OnConnectionEstablished( IStreamSocket &socket, const IAddress & /*address*/) { Output(_T("OnConnectionEstablished")); IQueueTimers::Handle timerHandle = m_timerQueue.CreateTimer(); socket.SetUserData(m_userDataIndex, timerHandle); if (socket.TryWrite(m_welcomeMessage.c_str(), GetStringLength<DWORD>(m_welcomeMessage))) { socket.TryRead(); } SetTimer(socket); } void CSocketServer::OnSocketReleased( IIndexedOpaqueUserData &userData) { IQueueTimers::Handle timerHandle = userData.GetUserData(m_userDataIndex); m_timerQueue.DestroyTimer(timerHandle); }
class IQueueTimers::Timer { public : /// User data that can be passed to Timer via the OnTimer() call when /// the timeout expires. typedef IQueueTimers::UserData UserData; /// Called after the timer expires. virtual void OnTimer( UserData userData) = 0; protected : /// We never delete instances of this interface; you must manage the /// lifetime of the class that implements it. ~Timer() {} };
void CSocketServer::SetTimer( IStreamSocket &socket) { IQueueTimers::Handle timerHandle = socket.GetUserData(m_userDataIndex); socket.AddRef(); // Take a reference to hold until the timer is cancelled or expires... static const Milliseconds timeout = 2000; // 2 seconds if (m_timerQueue.SetTimer(timerHandle, *this, timeout, reinterpret_cast<UserData>(&socket))) { socket.Release(); // Timer was already pending so a reference is already held } }
SetTimer()
will return true
and it's important to release the reference you just took before you called SetTimer()
as we only need to hold a single reference whilst we have a timer set (the timer only goes off once, if you call SetTimer()
whilst the timer is already set then all you're doing is changing the time when the timer will go off and so there's no need to take a new reference on the socket. However, you MUST take a reference before calling SetTimer()
as you can't tell if the timer is already set and there's a race condition if you wait until SetTimer()
returns false
before taking your reference. The timer handler might then look something like this: void CSocketServer::OnTimer( UserData userData) { Output(_T("OnTimer")); IStreamSocket &socket = *reinterpret_cast<IStreamSocket *>(userData); static const string message("Periodic per connection message\r\n"); socket.Write(message.c_str(), GetStringLength<DWORD>(message)); SetTimer(socket); socket.Release(); // Release the reference that we took when we set this // timer. }
void CSocketServer::OnConnectionClientClose( IStreamSocket &socket) { if (m_timerQueue.CancelTimer(timerHandle)) { socket.Release(); // The timer was active when it was cancelled, release the reference // that we took when we called SetTimer(). } }