这是交替执行线程的安全方法吗?

时间:2010-06-28 20:52:21

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

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

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循环更复杂。

3 个答案:

答案 0 :(得分:1)

  

“......这将是GUI应用......”

然后你可能不希望也不会在Main()中拥有上述顺序代码。

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

答案 1 :(得分: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()) ;
  }
}

答案 2 :(得分:0)

我建议任何时候考虑使用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没有为当前线程设置,然后返回等待