如何在C#中实现与定时器的同步

时间:2011-09-23 05:11:31

标签: c# .net multithreading synchronization

我有一个场景,我的C#类有两个方法,如DoThis()DoThat(),它们被外部调用者以任何顺序相互独立地调用。这两种方法需要以下列方式同步:

  • 致电DoThis()后,请等待至少t1秒再继续执行DoThat()
  • 致电DoThat()后,请等待至少t2秒再继续执行DoThis()

所以基本上是伪代码:

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()DoOtherStuff() 不是长时间运行的方法,否则不共享资源。通常不会同时调用DoThis()DoThat()。但我仍然需要防止潜在的僵局。

如何在C#中最好地实施DoThis()DoThat()

修改的 我现在的方案很简单,因为没有任意数量的线程调用这些函数。出于简化的目的,单个调用程序线程以任意顺序调用这些函数。因此,不会同时调用这两个方法,而是调用者将以任何顺序逐个调用这些方法。我无法控制调用者线程的代码,所以我想强制执行连续调用DoThis(),DoThat()之间的延迟。

3 个答案:

答案 0 :(得分:5)

使用定时锁存器很容易解决这个问题。锁存器是打开或关闭的同步机制。当允许打开的线程通过时。当封闭的线程无法通过时。定时锁存器是在经过一定时间后自动重新打开或重新闭合的锁存器。在这种情况下,我们需要一个“常开”闩锁,因此行为偏向于保持开放状态。这意味着锁存器将在超时后自动重新打开,但仅在显式调用Close时关闭。多次调用Close将重置计时器。

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();
}

我们可以像下面这样实现我们的定时锁存器。

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);
            }
        }
    }

}

答案 1 :(得分:1)

嗯......在这种情况下你需要什么: One Thread连续一段时间调用DoThis。另一个可以在最后一次调用DoThat之后至少t2秒运行DoThat,或者是在最后一次调用DoThat之后的第一个运行DoThat吗?

我认为,如果您的目标平台是Win,那么最好使用WaitableTimer(但是,它在.NET中没有实现,但您可以通过API使用它。您需要定义这些功能:

[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;

然后使用它如下:

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);
}

使用后你可以通过调用CloseHandle来破坏计时器。

答案 2 :(得分:0)

好的,我正在尝试使用EventWaitHandle来解决这个问题。寻找意见/反馈。这可以可靠地工作吗?

// 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);
    }
}

我计划使用它的方式:

// 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);
}