我有一个场景,我的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()之间的延迟。
最佳答案
使用定时锁存器很容易解决这个问题。闩锁是打开或关闭的同步机制。允许开放线程通过时。当封闭的线程无法通过时。定时锁存器是经过一定时间后将自动重新打开或重新关闭的锁存器。在这种情况下,我们需要一个“常开”锁存器,以便使行为偏向于保持打开状态。这意味着在超时后,闩锁将自动重新打开,但仅在显式调用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);
}
}
}
}