2011-09-23 90 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