Как реализовать синхронизацию с таймерами в C # - PullRequest
4 голосов
/ 23 сентября 2011

У меня есть сценарий, в котором мой класс 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() не будут вызываться одновременно.Но мне все еще нужно защищаться от потенциальных тупиков.

Как лучше всего реализовать DoThis(), DoThat() в C #?

РЕДАКТИРОВАТЬ Мой сценарий сейчас прост в том, что не существует произвольного числа потоков, вызывающих эти функции.Для упрощения существует один поток вызывающих, вызывающий эти функции в произвольной последовательности.Таким образом, эти два метода не будут вызываться одновременно, вместо этого вызывающий будет вызывать эти методы один за другим в любом порядке.У меня нет контроля над кодом потока вызывающей стороны, поэтому я хочу установить задержку между последовательными вызовами DoThis (), DoThat ().

Ответы [ 3 ]

5 голосов
/ 23 сентября 2011

Это довольно легко решить с помощью временной защелки.Защелка - это механизм синхронизации, который либо открыт, либо закрыт.Когда открытые темы могут проходить.Когда закрытые темы не могут пройти.Временная защелка - это та, которая автоматически открывается или закрывается по истечении определенного времени.В этом случае нам нужна «нормально открытая» защелка, чтобы поведение оставалось открытым.Это означает, что защелка вновь откроется автоматически после истечения времени ожидания, но закроется только в случае явного вызова 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 голос
/ 23 сентября 2011

Хм .. Что вам нужно в этом случае: один поток вызывает DoThis некоторое время подряд.Другой может запускать DoThat как минимум через t2 секунды после последнего вызова DoThat или первого после последнего вызова DoThat?

Я думаю, если ваша целевая платформа 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.

0 голосов
/ 24 сентября 2011

Хорошо, я пробую возможное решение этой проблемы с помощью 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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...