如何在 C# 中 "interrupt" while 循环和 return 到同一个地方(不使用任何线程)

How to "interrupt" a while loop in C# and return to the same place (without using any Threading)

我需要帮助在 while 循环之间切换并恢复到它们所在的确切状态。

例如:

while(1==1) 
{
    x++;
    x++;
    x++;
    x++;
}

while(1==1)
{
    Console.WriteLine("X=" + x);
    Console.WriteLine("X=" + x);
    Console.WriteLine("X=" + x);
    Console.WriteLine("X=" + x);
}

我正在为一个允许您在 C# 中创建 OS 的项目工作。它称为 Cosmos,快速 google 搜索应该会为您提供一些信息。

我需要做的是暂停其中一个循环,然后恢复(或开始)一个不同的循环直到时间结束,然后暂停该循环并恢复另一个循环,依此类推无限循环。

我正在尝试制作一个简单的任务调度程序,并且我计划进行比简单的 while 循环切换更多的更改,但我希望将其作为原始状态并用于测试。

因此,需要发生的是第一个 while 循环执行,然后暂停,然后执行第二个。需要发生的是每个循环都会暂停并切换到不同的循环,有效地看起来好像它们同时是 运行。所以,会发生的是 x 会增加,然后被打印,x 增加,等等。

好吧,我们可以通过创建状态机来处理每个循环来进行协作(非抢占式)多任务处理:

private interface IStateMachine
{
  void DoNext();
}

private class Loop0 : IStateMachine
{
  private int _state;
  private int x;

  public void DoNext()
  {
    switch (_state)
    {
      case 0:
        x++;
        _state = 1;
        break;
      case 1:
        x++;  // This is of course the same as previous, but I'm matching
            // the code in your question. There's no reason why it need
            // not be something else.
        _state = 2;
        break;
      case 2:
        x++;
        _state = 3;
        break;
      case 3:
        x++;
        _state = 0;
        break;
    }
  }
}

private class Loop1 : IStateMachine
{
  private int _state;
  private int x;

  public void DoNext()
  {
    switch (_state)
    {
      case 0:
        Console.WriteLine("X=" + x);
        _state = 1;
        break;
      case 1:
        Console.WriteLine("X=" + x);
        _state = 2;
        break;
      case 2:
        Console.WriteLine("X=" + x);
        _state = 3;
        break;
      case 3:
        Console.WriteLine("X=" + x);
        _state = 0;
        break;
    }
  }
}

private static void Driver()
{
  // We could have all manner of mechanisms for deciding which to call, e.g. keep calling one and
  // then the other, and so on. I'm going to do a simple time-based one here:
  var stateMachines = new IStateMachine[] { new Loop0(), new Loop1() };
  for (int i = 0;; i = (i + 1) % stateMachines.Length)
  {
    var cur = stateMachines [i];
    DateTime until = DateTime.UtcNow.AddMilliseconds (100);
    do
    {
      cur.DoNext ();
    } while (DateTime.UtcNow < until);
  }
}

这有两个大问题:

  1. 每个中的 x 是一个单独的 x。我们需要将 int 装箱或将其包装在引用类型中,以便两种方法都可以访问相同的变量。
  2. 您的循环与这些状态机之间的关系不是很清楚。

幸运的是,已经存在一种方法(实际上不止一种)可以在 C# 中编写一个方法,该方法被转换为一个状态机,其中包含一个用于移动到处理这两个问题的下一个状态的方法:

private static int x;

private static IEnumerator Loop0()
{
  for(;;)
  {
    x++;
    yield return null;
    x++;
    yield return null;
    x++;
    yield return null;
    x++;
    yield return null;
  }
}

private static IEnumerator Loop1()
{
  for(;;)
  {
    Console.WriteLine("X=" + x);
    yield return null;
    Console.WriteLine("X=" + x);
    yield return null;
    Console.WriteLine("X=" + x);
    yield return null;
    Console.WriteLine("X=" + x);
    yield return null;
  }
}

private static void Driver()
{
  // Again, I'm going to do a simple time-based mechanism here:
  var stateMachines = new IEnumerator[] { Loop0(), Loop1() };
  for (int i = 0;; i = (i + 1) % stateMachines.Length)
  {
    var cur = stateMachines [i];
    DateTime until = DateTime.UtcNow.AddMilliseconds (100);
    do
    {
      cur.MoveNext ();
    } while (DateTime.UtcNow < until);
  }
}

现在不仅可以很容易地看出这与您的循环有何关系(这两种方法中的每一个都有相同的循环,只是添加了 yield return 语句),而且共享 x也为我们处理,所以这个例子实际上显示它增加,而不是看不见的 x 递增和不同的 x 总是显示 0

我们还可以使用值 yielded 来提供有关我们的合作社 "thread" 想要做什么的信息。比如returning true总是放弃自己的时间片(相当于在C#多线程代码中调用Thread.Yield()):

private static int x;

private static IEnumerator<bool> Loop0()
{
  for(;;)
  {
    x++;
    yield return false;
    x++;
    yield return false;
    x++;
    yield return false;
    x++;
    yield return true;
  }
}

private static IEnumerator<bool> Loop1()
{
  for(;;)
  {
    Console.WriteLine("X=" + x);
    yield return false;
    Console.WriteLine("X=" + x);
    yield return false;
    Console.WriteLine("X=" + x);
    yield return false;
    Console.WriteLine("X=" + x);
    yield return true;
  }
}

private static void Driver()
{
  // The same simple time-based one mechanism, but this time each coroutine can
  // request that the rest of its time-slot be abandoned.
  var stateMachines = new IEnumerator<bool>[] { Loop0(), Loop1() };
  for (int i = 0;; i = (i + 1) % stateMachines.Length)
  {
    var cur = stateMachines [i];
    DateTime until = DateTime.UtcNow.AddMilliseconds (100);
    do
    {
      cur.MoveNext ();
    } while (!cur.Current && DateTime.UtcNow < until);
  }
}

因为我在这里使用 bool,所以我只有两个状态会影响 Driver()(我的简单调度程序)的行为。显然,更丰富的数据类型将允许更多选项,但也更复杂。

一种可能性是让您的编译器具有一种方法,该方法必须 return void(与 yieldawait 对 return 在 C# 中使用它们的方法类型),其中可能包含 thread opportunitythread yieldthread leave 等关键字,然后将映射到 yield return falseyield return true 和上面 C# 中的 yield break

当然,合作需要明确的代码来说明其他 "threads" 何时可能有机会 运行,在本例中由 yield return 完成。对于我们喜欢用 C# 为操作系统编写的那种抢占式多线程,它可以 运行 在其中时间片可以在任何时间结束,而不仅仅是我们明确允许的时间,这将要求您编译来源来产生这样的状态机,而无需在该来源中说明它们。这仍然是合作的,但在编译时会强制将合作排除在代码之外。

真正的抢占式多线程需要有某种方式来存储切换到另一个线程时每个循环的当前状态(就像 .NET 程序中每个线程的堆栈一样)。在虚拟 OS 中,您可以通过在底层 OS 的线程之上构建线程来做到这一点。在非虚拟 OS 中,您可能必须构建更接近金属的线程机制,调度程序会在线程更改时更改指令指针,