好吧,我们可以通过创建状态机来处理每个循环来进行协作(非抢占)多任务:
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);
}
}
这有两个大问题:
- 在
x
每个是一个单独的x
. 我们需要将其装箱int
或包装在引用类型中,以便两种方法都可以访问同一个变量。
- 你的循环和这些状态机之间的关系不是很清楚。
幸运的是,已经有一种方法(实际上不止一种)可以在 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
显示的不同。
我们还可以使用值yield
ed 来提供有关我们的合作“线程”想要做什么的信息。比如返回true
总是放弃它的时间片(相当于Thread.Yield()
在C#多线程代码中调用):
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
这里使用 a 时,我只有两个状态会影响Driver()
(我的简单调度程序)的行为方式。显然,更丰富的数据类型将允许更多选项,但更复杂。
一种可能性是让您的编译器具有一种必须返回的方法void
(类似于在 C# 中使用它们的方法的返回类型的方式yield
和await
限制),该方法可能包含关键字,例如thread opportunity
,thread yield
然后thread leave
将映射到yield return false
,yield return true
和yield break
在上面的 C# 中。
当然,要合作,它需要明确的代码来说明其他“线程”何时可能有机会运行,在这种情况下,由yield return
. 对于我们喜欢在 C# 中为它可以运行的操作系统编写的那种抢占式多线程,其中时间片可以在任何时候结束,而不是仅仅在我们明确允许的地方结束,这将要求您编译源代码以产生这样的状态机,而不是该源中的指令。这仍然是合作的,但在编译时会迫使合作脱离代码。
真正的抢占式多线程需要您在切换到另一个线程时有某种方式来存储每个循环的当前状态(就像 .NET 程序中每个线程的堆栈一样)。在虚拟操作系统中,您可以通过在底层操作系统线程之上构建线程来做到这一点。在非虚拟操作系统中,您可能必须构建更接近金属的线程机制,调度程序会在线程更改时更改指令指针,