我有一个场景,我的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()之间的延迟。
答案 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)
我认为,如果您的目标平台是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);
}