多线程环境中的事件

问题描述 投票:0回答:1

我正在尝试构建一个系统,通过该系统用户可以构建一些测试程序而无需知道如何编码。为此,我以这种方式设计了系统,即有一个过程,可以包含其他过程或步骤。这些步骤可以包含命令。该过程包含逻辑,以什么顺序发生。这些步骤包含下一步要连接到的信息。

[这些步骤和命令由Execute调用,并在完成后立即调用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;
    }
}

我已经在某些地方测试了是否正在停止,并试图拦截已完成的操作,以便在停止之后不执行该操作,并且会造成任何麻烦。这种方式似乎并不能完全防水,但是目前看来它是可行的。有没有一种通过设计提供这种安全性的方法?我可能这样做完全错误吗?

c# multithreading events deadlock race-condition
1个回答
0
投票

您的共享状态未得到一致的并发访问保护。例如,以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语句将不会抱怨(因为基础的lock类允许重新输入),但是这会使您的程序难以理解和可维护。

© www.soinside.com 2019 - 2024. All rights reserved.