2

I need help switching between while loops and resuming to the exact state that they were in.

An example would be this:

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);
}

I am working for a project that lets you create an OS in C#. It is called Cosmos and a quick google search should land you with some info.

What I need to do is to pause one of the loops, and resume (or start) a different loop until the time is over, then that loop will be paused and a different one will be resumed and so on in an infinite cycle.

I am trying to make a simple task scheduler, and I plan to make a lot more changes than simple while loop switching, but I would like this as a primitive state and for testing.

So, what would need to happen is that one while loop executes, then is paused and the second one is executed. What would need to happen is that each loop would pause and switch to a different one, effectively seeming as if they are running at the same time. So, what would happen is that x would increase, then be printed, x increased, and so on.

Cyber
  • 33
  • 10
  • 2
    You might be able to rig something up using `yield return`, but it won't be pretty or easy to use. I'm not sure there is any way to do this using the exact format you have in your example. – Bradley Uffner Nov 24 '15 at 18:17
  • 1
    It's not possible to write a scheduler like this at the level .NET provides. The best you can do is manually decide when to stop looping, save the state yourself, and then restore it when you continue. – Collin Dauphinee Nov 24 '15 at 18:24
  • 1
    What exactly are you trying to accomplish? As it stands, your question asks for something that isn't readily available in imperative languages like C#. If you'll explain what you need it for, or why you can't use any existing task scheduling libraries, we might be able to help more. – Avner Shahar-Kashtan Nov 24 '15 at 18:24
  • 1
    It's likely that you'll get more useful answers if you can share the reason that you think you need to switch between the loops. Are you trying to avoid the existing threading frameworks for any particular reason that we might help you work around, or is this requirement dictated by a professor or PHB? Do the loops have to be written as if they have no idea they might be interrupted, or can you craft their code to specifically allow this? Are the loops themselves even necessary, or is that the only way you could think of to accomplish some specific behavior you want? etc. – StriplingWarrior Nov 24 '15 at 18:26
  • @CollinDauphinee how would I save the state of a loop? Also if it helps I have access to basic assembly stuff. – Cyber Nov 24 '15 at 18:26
  • @Cyber: Are you planning to write your own .NET IL interpreter/runtime, then? That seems like a big undertaking. – StriplingWarrior Nov 24 '15 at 18:27
  • @StriplingWarrior and Avner Shahar-Kashtan I am helping a project called COSMOS which lets you build your own OS in C#. There is currently no threading at all so I would like to help implement some. – Cyber Nov 24 '15 at 18:28
  • I am simply trying to implement very simple threading for primitive testing. I have been working on threading recently with things like context switching. – Cyber Nov 24 '15 at 18:30
  • 4
    @Cyber: In that case, I think the question is a little too broad. It sounds like you're asking "how do I implement context-switching in a kernel," to which a complete answer would require an entire textbook chapter, and a correct answer would probably require knowledge of Cosmos's specific kernel and architecture. If you run into problems with a specific aspect of this, we might be able to help more. – StriplingWarrior Nov 24 '15 at 18:37
  • I am not asking on how I implement context-switching in a kernel, I'm just wondering if there is a simple way to switch between while loops or if there is something a lot bigger needed – Cyber Nov 24 '15 at 22:08
  • c# await async does this, allows you to jump back into code at arbitrary points – pm100 Nov 25 '15 at 01:58

1 Answers1

3

Well, we could do coöperative (nonpreëmptive) multi-tasking by creating state machines to handle each loop:

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);
  }
}

There are two big problems with this:

  1. The x in each is a separate x. We need to box the int or wrap it in a reference type so that both methods can be accessing the same variable.
  2. The relationship between your loops and these state machines isn't very clear.

Luckily there already exists a way (indeed more than one) to write a method in C# that is turned into a state-machine with a method for moving to the next state that handles both of these issues:

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);
  }
}

Now not only is it easy to see how this relates to your loops (each of the two methods have the same loop, just with added yield return statements), but the sharing of x is handled for us too, so this example actually shows it increasing, rather than an unseen x incrementing and a different x that is always 0 being displayed.

We can also use the value yielded to provide information about what our coöperative "thread" wants to do. For example, returning true to always give up its time slice (equivalent to calling Thread.Yield() in C# multi-threaded code):

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);
  }
}

As I'm using a bool here I have only two states that affect how Driver() (my simple scheduler) acts. Obviously a richer datatype would allow for more options, but be more complex.

One possibility would be to have your compiler have a type of method that must return void (comparable to how yield and await have restrictions on the return types of methods that use them in C#) which could contain keywords like thread opportunity, thread yield and thread leave which would then be mapped to yield return false, yield return true and yield break in the C# above.

Of course, being coöperative it requires explicit code to say when other "threads" might have an opportunity to run, which in this case is done by the yield return. For the sort of preëmptive multi-threading that we enjoy just writing in C# for the operating systems it can run on, where time slices can end at any point rather than just where we explicitly allow it will require you to compile the source to produce such state machines, without their being instructions in that source. This is still coöperative, but forces that coöperation out of the code when compiling.

Truly preëmptive multi-threading would require that you have some way of storing the current state of each loop when switching to another thread (just as the stack of each thread in a .NET program does). In a virtual OS you could do this by building threads on top of the underlying OS's threads. In a non-virtual OS you're likely going to have to build your threading mechanism closer to the metal, with the scheduler changing the instruction pointer when threads change,

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251