簡體   English   中英

多線程環境中的事件

[英]Events in Multi Threaded Environment

我正在嘗試構建一個系統,通過該系統,用戶可以在不知道如何編碼的情況下構建一個小測試程序。 為此,我以這種方式設計了系統,即有一個程序,其中可以包含其他程序或步驟。 步驟可以包含命令。 該過程包含以何種順序發生的邏輯。 這些步驟包含它們接下來連接到哪個步驟的信息。

步驟和命令由Execute調用OnDone在它們完成時調用OnDone ,這可能直接發生(例如IncreaseCommand )或在一段時間后( WaitCommand或任何其他與連接的硬件通信的命令;兩者都在不同的線程上)。 此外,它們可以通過超時或用戶停止。

只要沒有超時,一切正常。 如果有超時,我會嘗試通過鎖定來使代碼線程安全。 此外,當超時停止在同一時刻完成其工作的命令(例如WaitCommand )時,還有這些陷阱。 因此,有一個線程從過程通過步驟到命令,發出停止信號,另一個線程從命令通過步驟到過程信號完成。

我添加了一些代碼片段,我已經剝離了大部分處置代碼和其他內部內容,這似乎與問題無關。

public sealed class Procedure : IStep, IStoppable
{
    public event EventHandler Done;
    public event EventHandler Stopped;
    public event EventHandler TimedOut;
    public void Run()
    {
        if (!IsRunning)
        {
            CheckStartTimer();
            Start(First);
        }
    }
    private void CheckStartTimer()
    {
        isTimerUnlinked = false;
        timer.Elapsed += OnTimedOut;
        timer.IntervalInMilliseconds = (int)Timeout.TotalMilliseconds;
        timer.Start();
    }
    private void OnTimedOut(object sender, EventArgs e)
    {
        if (isTimerUnlinked)
            return;
        stopFromTimeout = true;
        Stop();
    }
    private void Start(IStep step)
    {
        isStopped = false;
        isStopping = false;
        Active = step;
        LinkActive();
        active.Run();
    }
    private void LinkActive()
    {
        active.Done += OnActiveFinished;
        if (active is Procedure proc)
            proc.TimedOut += OnActiveFinished;
    }
    private void OnActiveFinished(object sender, EventArgs e)
    {
        UnlinkActive();
        lock (myLock)
        {
            if (isStopped)
                return;
            if (stopFromTimeout)
            {
                OnStopped();
                return;
            }
        }
        var successor = active.ActiveSuccessor;
        if (successor == null)
            OnDone();
        else if (isStopping || timeoutPending || stopFromTimeout)
            OnStopped();
        else
            Start(successor);
    }
    public void Stop()
    {
        if (isStopping)
            return;
        isStopping = true;
        StopTimer();
        if (active is IStoppable stoppable)
        {
            stoppable.Stopped += stoppable_Stopped;
            stoppable.Stop();
        }
        else
            OnStopped();
    }
    private void stoppable_Stopped(object sender, EventArgs e)
    {
        var stoppable = sender as IStoppable;
        stoppable.Stopped -= stoppable_Stopped;
        OnStopped();
    }
    private void OnStopped()
    {
        isStopping = false;
        lock (myLock)
        {
            isStopped = true;
        }
        UnlinkActive();
        lock (myLock)
        {
            Active = null;
        }
        if (stopFromTimeout || timeoutPending)
        {
            stopFromTimeout = false;
            timeoutPending = false;
            CleanUp();
            TimedOut?.Invoke(this, EventArgs.Empty);
        }
        else
            Stopped?.Invoke(this, EventArgs.Empty);
    }
    private void UnlinkActive()
    {
        if (stopFromTimeout && !isStopped)
            return;
        lock (myLock)
        {
            if (active == null)
                return;
            active.Done -= OnActiveFinished;
            var step = active as IStep;
            if (step is Procedure proc)
                proc.TimedOut -= OnActiveFinished;
        }
    }
    private void OnDone()
    {
        CleanUp();
        Done?.Invoke(this, EventArgs.Empty);
    }
    private void CleanUp()
    {
        Reset();
        SetActiveSuccessor();
    }
    private void Reset()
    {
        Active = null;
        stopFromTimeout = false;
        timeoutPending = false;
        StopTimer();
    }
    private void StopTimer()
    {
        if (timer == null)
            return;
        isTimerUnlinked = true;
        timer.Elapsed -= OnTimedOut;
        timer.Stop();
    }
    private void SetActiveSuccessor()
    {
        ActiveSuccessor = links[(int)Successor.Simple_If];
    }
}
internal sealed class CommandStep : IStep, IStoppable
{
    public event EventHandler Done;
    public event EventHandler Started;
    public event EventHandler Stopped;
    public CommandStep(ICommand command)
    {
        this.command = command;
    }
    public void Run()
    {
        lock (myLock)
        {
            stopCalled = false;
            if (cookie != null && !cookie.Signalled)
                throw new InvalidOperationException(ToString() + " is already active.");
            cookie = new CommandStepCookie();
        }
        command.Done += OnExit;
        unlinked = false;
        if (stopCalled)
            return;
        command.Execute();
    }
    public void Stop()
    {
        stopCalled = true;
        if (command is IStoppable stoppable)
            stoppable.Stop();
        else
            OnExit(null, new CommandEventArgs(ExitReason.Stopped));
    }
    private void OnExit(object sender, CommandEventArgs e)
    {
        (sender as ICommand).Done -= OnExit;
        lock (myLock)
        {
            if (cookie.Signalled)
                return;
            cookie.ExitReason = stopCalled ? ExitReason.Stopped : e.ExitReason;
            switch (cookie.ExitReason)
            {
                case ExitReason.Done:
                default:
                    if (unlinked)
                        return;
                    Unlink();
                    ActiveSuccessor = links[(int)Successor.Simple_If];
                    break;
                case ExitReason.Stopped:
                    Unlink();
                    break;
                case ExitReason.Error:
                    throw new NotImplementedException();
            }
            cookie.Signalled = true;
        }
        if (cookie.ExitReason.HasValue)
        {
            active = false;
            if (cookie.ExitReason == ExitReason.Done)
                Done?.Invoke(this, EventArgs.Empty);
            else if (cookie.ExitReason == ExitReason.Stopped)
                stopCalled = false;
                Stopped?.Invoke(this, EventArgs.Empty);
        }
    }
    private void Unlink()
    {
        if (command != null)
            command.Done -= OnExit;
        unlinked = true;
    }
}
internal sealed class WaitCommand : ICommand, IStoppable
{
    public event EventHandler<CommandEventArgs> Done;
    public event EventHandler Stopped;
    internal WaitCommand(ITimer timer)
    {
        this.timer = timer;
        timer.AutoRestart = false;
        TimeSpan = TimeSpan.FromMinutes(1);
    }
    public void Execute()
    {
        lock (myLock)
        {
            cookie = new WaitCommandCookie(
                e => Done?.Invoke(this, new CommandEventArgs(e)));
            timer.IntervalInMilliseconds = (int)TimeSpan.TotalMilliseconds;
            timer.Elapsed += OnElapsed;
        }
        timer.Start();
    }
    private void OnElapsed(object sender, EventArgs e)
    {
        OnExit(ExitReason.Done);
    }
    public void Stop()
    {
        if (cookie == null)
        {
            Done?.Invoke(this, new CommandEventArgs(ExitReason.Stopped));
            return;
        }
        cookie.Stopping = true;
        lock (myLock)
        {
            StopTimer();
        }
        OnExit(ExitReason.Stopped);
    }
    private void OnExit(ExitReason exitReason)
    {
        if (cookie == null)
            return;
        lock (myLock)
        {
            if (cookie.Signalled)
                return;
            Unlink();
            if (cookie.Stopping && exitReason != ExitReason.Stopped)
                return;
            cookie.Stopping = false;
        }
        cookie.Signal(exitReason);
        cookie = null;
    }
    private void StopTimer()
    {
        Unlink();
        timer.Stop();
    }
    private void Unlink()
    {
        timer.Elapsed -= OnElapsed;
    }
}

我一直在某些地方測試停止是否正在進行並試圖攔截完成,以便在停止后不執行並造成任何麻煩。 這種方式似乎並不完全防水,盡管目前它似乎有效。 有沒有辦法通過設計提供這種安全性? 我可能這樣做完全錯誤嗎?

您的共享狀態不會始終受到並發訪問的保護。 isStopped字段為例:

private void OnStopped()
{
    isStopping = false;
    lock (myLock)
    {
        isStopped = true;
    }
    //...

private void Start(IStep step)
{
    isStopped = false;
    //...

首先它受到保護,其次它不是。 您可以選擇在任何地方保護它,也可以選擇不保護它。 沒有回旋的余地。 部分保護它與根本不保護它一樣好。

作為旁注,不建議在持有鎖時調用事件處理程序。 事件處理程序可能包含長時間運行的代碼,或調用可能受其他鎖保護的任意代碼,從而可能導致死鎖。 關於鎖定的一般建議是盡快釋放它。 持有鎖的時間越長,線程之間產生的爭用就越多。 因此,例如在方法OnActiveFinished

lock (myLock)
{
    if (isStopped)
        return;
    if (stopFromTimeout)
    {
        OnStopped();
        return;
    }
}

您在持有鎖的同時調用OnStopped OnStopped您調用處理程序:

Stopped?.Invoke(this, EventArgs.Empty);

正確的方法是在釋放鎖后調用OnStopped 使用局部變量來存儲有關是否調用它的信息:

var localInvokeOnStopped = false;
lock (myLock)
{
    if (isStopped)
        return;
    if (stopFromTimeout)
    {
        localInvokeOnStopped = true;
    }
}
if (localInvokeOnStopped)
{
    OnStopped();
    return;
}

最后一個建議,避免在同一個鎖上遞歸鎖定。 如果你這樣做, lock語句不會抱怨(因為底層Monitor類允許重入),但它會使你的程序更難以理解和維護。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM