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

amo*_*amo 4 .net c# 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);
}
Run Code Online (Sandbox Code Playgroud)

DoStuff()DoOtherStuff()长时间运行的方法,不以其他方式共享资源.通常DoThis()DoThat()不会同时调用.但我仍然需要防止潜在的僵局.

我怎样才能最好地实现DoThis(),DoThat()在C#中?

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

Bri*_*eon 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();
}
Run Code Online (Sandbox Code Playgroud)

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

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

}
Run Code Online (Sandbox Code Playgroud)