【问题标题】:Is this a safe way to execute threads alternatively?这是交替执行线程的安全方法吗?
【发布时间】:2010-06-28 20:52:21
【问题描述】:

我想交替运行代码,所以我可以随时停止执行。这段代码安全吗?

static class Program
{
    static void Main()
    {
        var foo = new Foo();
        //wait for interaction (this will be GUI app, so eg. btnNext_click)
        foo.Continue();
        //wait again etc.
        foo.Continue();
        foo.Continue();
        foo.Continue();
        foo.Continue();
        foo.Continue();
    }
}

class Foo
{
    public Foo()
    {
        new Thread(Run).Start();
    }

    private void Run()
    {
        Break();
        OnRun();
    }

    protected virtual void OnRun()
    {
        for (var i = 0; i < 5; i++)
        {
            Console.WriteLine(i);
            Break();
        }
        //do something else and break;
    }

    private void Break()
    {
        lock (this)
        {
            Monitor.Pulse(this);
            Monitor.Wait(this);
        }
    }

    public void Continue()
    {
        lock (this)
        {
            Monitor.Pulse(this);
            Monitor.Wait(this);
        }
    }
}

我当然知道,现在申请永远不会结束,但这不是重点。

我需要这个,因为我想展示某种算法的步骤并描述特定时刻发生的事情,即使在代码。例如那些行:

for (var i = 0; i < 5; i++)
{
    Console.WriteLine(i);
    Break();
}

然后应该替换为:

if (this.i < 5)
{
    Console.WriteLine(i++);
}

这只是我想要展示的一个小例子。代码将比虚拟的for 循环更复杂。

【问题讨论】:

  • 这很奇怪。只是不要使用线程。
  • @Hans Passant:你会怎么做这种......自我解释(?)代码?在OnRun 内部,我想运行与图形相关的算法并向用户展示此时发生的事情的解释,例如。带有消息和顶点/边缘着色。将这样的东西分成小块编码会很痛苦。
  • 您对问题的措辞使您似乎不熟悉 Windows 窗体的 UI 线程模型。您应该解释需要与一些单独的后台线程“交替”的事件不能做什么。
  • @David,您不能暂停代码执行并等待用户按下按钮,然后在某个方法的中间恢复代码执行。
  • 另外我不认为这里有另一个臃肿的框架,因为设置这种类型的代码相对简单。 看我的回答:stackoverflow.com/questions/3136039/…

标签: c# .net multithreading synchronization thread-safety


【解决方案1】:

我建议您查看blog post 关于实现光纤的信息。

代码 (以防网站出现故障。)

public class Fiber
{
    private readonly Stack<IEnumerator> stackFrame = new Stack<IEnumerator>();
    private IEnumerator currentRoutine;

    public Fiber(IEnumerator entryPoint)
    {
        this.currentRoutine = entryPoint;
    }

    public bool Step()
    {
        if (currentRoutine.MoveNext())
        {
            var subRoutine = currentRoutine.Current
                           as IEnumerator;
            if (subRoutine != null)
            {
                stackFrame.Push(currentRoutine);
                currentRoutine = subRoutine;
            }
        }
        else if (stackFrame.Count > 0)
        {
            currentRoutine = stackFrame.Pop();
        }
        else
        {
          OnFiberTerminated(
              new FiberTerminatedEventArgs(
                  currentRoutine.Current
              )
          );
          return false;
      }

      return true;
    }

    public event EventHandler<FiberTerminatedEventArgs> FiberTerminated;

    private void OnFiberTerminated(FiberTerminatedEventArgs e)
    {
        var handler = FiberTerminated;
        if (handler != null)
        {
            handler(this, e);
        }
    }
}

public class FiberTerminatedEventArgs : EventArgs
{
  private readonly object result;

  public FiberTerminatedEventArgs(object result)
  {
      this.result = result;
  }

  public object Result
  {
      get { return this.result; }
  }
}   

class FiberTest
{
  private static IEnumerator Recurse(int n)
  {
      Console.WriteLine(n);
      yield return n;
      if (n > 0)
      {
          yield return Recurse(n - 1);
      }
  }

  static void Main(string[] args)
  {
      var fiber = new Fiber(Recurse(5));
      while (fiber.Step()) ;
  }
}

【讨论】:

  • 非常感谢您的链接。这对我有很大帮助。我真的不需要全班,至少现在是这样。但是,使用Iteratoryield 关键字来中断(暂停)执行的想法很棒。问候
【解决方案2】:

"...这将是 GUI 应用..."

那么您可能不希望也不会在Main() 中使用上述顺序代码。

即主 GUI 线程不会像上面那样执行串行代码,但通常处于空闲状态,重新绘制等或处理 Continue 按钮单击。
在该事件处理程序中,您最好使用Auto|ManualResetEvent 来指示工作人员继续。
在worker中,只需等待事件。

【讨论】:

  • 谢谢。这很明显,但就像@Hans Passant 指出的那样,这有点奇怪,不知何故我没想过只使用AutoResetEvent
【解决方案3】:

我建议,任何时候考虑使用Monitor.Wait(),都应该编写代码,以便在Wait 有时自发地表现得好像它收到了一个脉冲时它可以正常工作。通常,这意味着应该使用该模式:

lock(monitorObj)
{
  while(notYetReady)
    Monitor.Wait(monitorObj);
}

对于您的情况,我建议您执行以下操作:

lock(monitorObj)
{
  turn = [[identifier for this "thread"]];
  Monitor.PulseAll(monitorObj);
  while(turn != [[identifier for this "thread"]])
    Monitor.Wait(monitorObj);
}

turn 无法在检查是否轮到当前线程继续执行和Monitor.Wait 之间进行更改。因此,如果Wait 没有被跳过,PulseAll 肯定会唤醒它。请注意,如果Wait 自发地表现得好像它收到了一个脉冲,代码会正常工作——它会简单地旋转,观察turn 没有为当前线程设置,然后返回等待。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-21
    相关资源
    最近更新 更多