amo
amo

Reputation: 325

How to implement synchronization with timers in C#

I have a scenario where my C# class has two methods say DoThis() and DoThat() that are called independent of each other, in any order, by an external caller. The two methods need to be synchronized in the following way:

So essentially in pseudocode:

static SomeCustomTimer Ta, Tb;
static TimeSpan t1, t2;

public static void DoThis()
{
    if(Tb.IsRunning())
        Tb.WaitForExpiry();

    DoStuff();
    Ta.Start(t1);
}

public static void DoThat()
{
    if(Ta.IsRunning())
        Ta.WaitForExpiry();

    DoOtherStuff();
    Tb.Start(t2);
}

DoStuff() and DoOtherStuff() are not long-running methods and do not share resources otherwise. Typically DoThis() and DoThat() will not be called concurrently. But I still need to protect against potential deadlocks.

How can I best implement DoThis(), DoThat() in C#?

EDIT My scenario right now is simple in that there aren't an arbitrary number of threads calling these functions. For purpose of simplification, there's a single caller thread calling these functions in an arbitrary sequence. So the two methods will not be called concurrently, instead the caller will call these methods one-by-one in any order. I don't have control over the caller thread's code so I want to enforce the delay between successive calls to DoThis(), DoThat().

Upvotes: 5

Views: 4753

Answers (3)

amo
amo

Reputation: 325

Okay I'm trying out a possible solution to this problem using EventWaitHandle. Looking for comments / feedback. Can this work reliably?

// Implementation of a manual event class with a DelayedSet method
// DelayedSet will set the event after a delay period
// TODO: Improve exception handling
public sealed class DelayedManualEvent : EventWaitHandle
{
    private SysTimer timer; // using SysTimer = System.Timers.Timer;

    public DelayedManualEvent() :
        base(true, EventResetMode.ManualReset)
    {
        timer = new SysTimer();
        timer.AutoReset = false;
        timer.Elapsed +=new ElapsedEventHandler(OnTimeout);
    }

    public bool DelayedSet(TimeSpan delay)
    {
        bool result = false;
        try
        {
            double timeout = delay.TotalMilliseconds;
            if (timeout > 0 && timer != null && Reset())
            {
                timer.Interval = timeout;
                timer.Start();
                result = true;
                Trace.TraceInformation("DelayedManualEvent.DelayedSet Event will be signaled in {0}ms",
                    delay);
            }
        }
        catch (Exception e)
        {
            Trace.TraceError("DelayedManualEvent.DelayedSet Exception {0}\n{1}", 
                e.Message, e.StackTrace);
        }
        return result;
    }

    private void OnTimeout(object source, ElapsedEventArgs e)
    {
        if (timer != null)
        {
            timer.Stop();
            Trace.TraceInformation("DelayedManualEvent.OnTimeout Event signaled at time {0}", e.SignalTime);
        }
        try
        {
            if (!Set())
            {
                Trace.TraceError("DelayedManualEvent.OnTimeout Event set failed");
            }
        }
        catch (Exception ex)
        {
            Trace.TraceError("DelayedManualEvent.OnTimeout Exception in signaling event\n{0}]\n{1}",
                ex.Message, ex.StackTrace);
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (timer != null)
        {
            timer.Dispose();
        }
        base.Dispose(disposing);
    }
}

The way I'm planning to use this:

// Pseudocode
static DelayedManualEvent delayedEvent = new DelayedManualEvent();
static TimeSpan t1, t2, maxTimeout;

public static void DoThis()
{
    if(!delayedEvent.WaitOne(maxTimeout))
        return;
    DoStuff();
    delayedEvent.DelayedSet(t1);
}

public static void DoThat()
{
    if(!delayedEvent.WaitOne(maxTimeout))
        return;
    DoOtherStuff();
    delayedEvent.DelayedSet(t2);
}

Upvotes: 0

Brian Gideon
Brian Gideon

Reputation: 48959

This is pretty easy to solve with a timed latch. A latch is synchronization mechanism that is either opened or closed. When open threads are allowed to pass through. When closed threads cannot get through. A timed latch is one that will automatically reopen or reclose after a certain amount of time has elapsed. In this case we want a "normally opened" latch so the behavior is biased towards staying open. That means the latch will reopen automatically after the timeout, but close only if Close is explicitly called. Multiple calls to Close will reset the timer.

static NormallyOpenTimedLatch LatchThis = new NormallyOpenTimedLatch(t2);
static NormallyOpenTimedLatch LatchThat = new NormallyOpenTimedLatch(t1);

static void DoThis()
{
  LatchThis.Wait();  // Wait for it open.

  DoThisStuff();

  LatchThat.Close();
}

static void DoThat()
{
  LatchThat.Wait(); // Wait for it open.

  DoThatStuff();

  LatchThis.Close();
}

And we can implement our timed latch like the following.

public class NormallyOpenTimedLatch
{
    private TimeSpan m_Timeout;
    private bool m_Open = true;
    private object m_LockObject = new object();
    private DateTime m_TimeOfLastClose = DateTime.MinValue;

    public NormallyOpenTimedLatch(TimeSpan timeout)
    {
        m_Timeout = timeout;
    }

    public void Wait()
    {
        lock (m_LockObject)
        {
            while (!m_Open)
            {
                Monitor.Wait(m_LockObject);
            }
        }
    }

    public void Open()
    {
        lock (m_LockObject)
        {
            m_Open = true;
            Monitor.PulseAll(m_LockObject);
        }
    }

    public void Close()
    {
        lock (m_LockObject)
        {
            m_TimeOfLastClose = DateTime.UtcNow;
            if (m_Open)
            {
                new Timer(OnTimerCallback, null, (long)m_Timeout.TotalMilliseconds, Timeout.Infinite);
            }
            m_Open = false;
        }
    }

    private void OnTimerCallback(object state)
    {
        lock (m_LockObject)
        {
            TimeSpan span = DateTime.UtcNow - m_TimeOfLastClose;
            if (span > m_Timeout)
            {
                Open();
            }
            else
            {
                TimeSpan interval = m_Timeout - span;
                new Timer(OnTimerCallback, null, (long)interval.TotalMilliseconds, Timeout.Infinite);
            }
        }
    }

}

Upvotes: 5

Tadeusz
Tadeusz

Reputation: 6913

Hm..What do you need in that case: One Thread calls DoThis some time in succession. Another can run DoThat at least t2 seconds after LAST calling of DoThat or the first one after last calling DoThat?

I think, If your target platform is Win then It is better to use WaitableTimer (however, It is not realized in .NET but you can use It through API. You need to define those functions:

[DllImport("kernel32.dll")]
 public static extern IntPtr CreateWaitableTimer(IntPtr lpTimerAttributes, bool bManualReset, string lpTimerName);

 [DllImport("kernel32.dll")]
 public static extern bool SetWaitableTimer(IntPtr hTimer, [In] ref long pDueTime,
                         int lPeriod, IntPtr pfnCompletionRoutine,
                         IntPtr lpArgToCompletionRoutine, bool fResume);

 [DllImport("kernel32", SetLastError = true, ExactSpelling = true)]
 public static extern Int32 WaitForSingleObject(IntPtr handle, int milliseconds);
 public static uint INFINITE = 0xFFFFFFFF;

And then using It as follow:

private IntPtr _timer = null;

//Before first call of DoThis or DoThat you need to create timer:
//_timer = CreateWaitableTimer (IntPtr.Zero, true, null);

public static void DoThis()
{
    //Waiting until timer signaled
    WaitForSingleObject (_timer, INFINITE);

    DoStuff();
    long dueTime = 10000 * 1000 * seconds; //dueTime is in 100 nanoseconds
    //Timer will signal once after expiration of dueTime
    SetWaitableTimer (_timer, ref dueTime, 0, IntPtr.Zero, IntPtr.Zero, false);
}

public static void DoThis()
{
    //Waiting until timer signaled
    WaitForSingleObject (_timer, INFINITE);

    DoOtherStuff();
    long dueTime = 10000 * 1000 * seconds; //dueTime is in 100 nanoseconds
    //Timer will signal once after expiration of dueTime
    SetWaitableTimer (_timer, ref dueTime, 0, IntPtr.Zero, IntPtr.Zero, false);
}

And after using you may destroy timer by calling CloseHandle.

Upvotes: 1

Related Questions