2011-09-23 63 views
4

我有一个场景,我的C#类有两种方法,分别称为DoThis()DoThat(),它们以任何顺序被外部调用者独立调用。这两种方法都需要通过以下方式进行同步:如何在C#中实现与定时器的同步

  • DoThis()通话结束后,与DoThat()执行
  • 着手DoThat()通话结束后等待至少t1秒,在继续之前等待至少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()不会被同时调用。但我仍然需要防范潜在的僵局。

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

编辑 我现在的情况很简单,因为没有任意数量的线程调用这些函数。为简化起见,有一个调用者线程以任意顺序调用这些函数。所以这两个方法不会被同时调用,而是调用者将以任何顺序逐一调用这些方法。我没有在调用者线程的代码控制,所以我想执行到DoThis(),找时间做()连续调用之间的延迟。

+0

只是想检查:你要“interupt”的其他线程的一段时间执行 - 在现在身在何处执行它是? (所以线程变得根本不需要处理器时间)?我想你可以试着用线程来解决这个问题,但我认为这不会完全预防死锁 - 如果一个线程持有一个锁,并且你暂停它,你希望获得什么? – Carsten

+0

这不会为每次调用“DoThis”或“DoThat”时创建一个无限但延迟的循环吗?那是你要的吗? – Enigmativity

+0

@CarstenKönig不,我不想中断线程执行。我想阻止并发执行,如果另一个线程在一段时间内完成,我也想延迟启动一个线程。 – amo

回答

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

Hm..What你在这种情况下,需要: 一个线程调用DoThis连续一段时间。另一个可以在最后一次调用DoThat之后至少t2秒后运行DoThat,或者在最后一次调用DoThat之后第一个运行DoThat? 。

我认为,如果你的目标平台是赢那么最好是使用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

出于同样的目的,您可以使用'AutoResetEvent' [(在MSDN上描述)](http://msdn.microsoft.com/zh-cn/library/system.threading.autoresetevent.aspx)。无需P/Invoke。 AutoResetEvents“WaitOne(Int32)”将一直等到它发出信号或时间用完。 –

+0

@ Praetor12我没有完全清晰地表达我的问题,但看起来像你确实得到了我的意图。您提出的解决方案听起来像可以为我工作。如果我可以找出一种不用p/invoke的方法,它会帮助很多。 – amo

+1

@bflat当一个线程在DoThat中时,你是否需要另一个线程不能在DoThis中输入,反之亦然?大量的线程可以同时在DoThat(或做到这一点)吗?这些问题必须回答,直到我们开始思考:) – Vasya

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

'DelayedSet'方法不是线程安全的。此外,你的主代码现在有一个不同于你原来的问题的行为。为什么'DoThis'和'DoThat'中的'return'语句? –

+0

如果你提出的是对你的问题的更新,习惯上编辑你的问题来包含它,而不是将它作为答案添加。但是,要回答您的反馈请求,不,这不起作用,很大程度上是由于您在这两种情况下使用了相同的DelayedManualEvent实例。此外,您的实现允许同时使用任意数量的线程,充当泛洪门:所有线程都等待,或者所有线程一次输入两种方法。请参阅Brian Gideon的答案,而不是你应该做的。 –

+0

@ChrisHannon道歉,我的第一个问题,所以我没有意识到习惯。下次会记住这一点。 关于反馈,我认为我仍然无法正确表达我的问题 - 情景更简单。我会编辑我的问题来澄清。 – amo