A simple, non-preemptive coroutine scheduler that allows for cooperative multitasking within Cosmos kernels.
This project was created to demonstrate the ability to use C#'s iterator support to achieve cooperative multitasking.
Cosmos.Coroutines has the following limitations:
- non-preemptive; you need to do a
yield return
to hand back control to the coroutine scheduler - basic round-robin; no priority system
- CPU halting - which occurs in many parts of Cosmos - will also halt the coroutine pool scheduler
Other than the caveats mentioned above, the coroutine system can act like a cooperative kernel task scheduler.
Cosmos.Coroutines is available on NuGet; either use the NuGet package manager in your IDE of choice, or, in a package manager terminal, type in:
NuGet\Install-Package Cosmos.Coroutines -Version 1.0.1
The following classes are included in the Cosmos.System.Coroutines
namespace:
Coroutine
- represents a coroutine, which can belong to only oneCoroutinePool
.CoroutinePool
- manages multiple coroutines. A globalCoroutinePool
is allocated on startup and can be accessed usingCoroutinePool.Main
. This pool will not affect the execution of the OS in any way until theStartPool
instance method is called.CoroutineControlPoint
- an object returned by valid coroutine implementations of theIEnumerator
interface, and accepted by theCoroutine
constructor. Specifies whether the coroutine should be ticked at a given time.WaitFor
- aCoroutineControlPoint
that waits for the specified amount of nanoseconds.WaitUntil
- aCoroutineControlPoint
that waits until a given condition is met.WaitIndefinetly
- aCoroutineControlPoint
that halts the coroutine until it's explicitly un-halted through said control point.
To use the main CoroutinePool
, simply do:
CoroutinePool.Main.StartPool();
To create a coroutine:
var coroutine = new Coroutine(MyCoroutine1());
coroutine.Start(); // will run the coroutine on the main pool; to run it in another, use CoroutinePool.AddCoroutine
// ...
IEnumerator<CoroutineControlPoint> MyCoroutine1()
{
while(true) {
Console.WriteLine("This prints every second.");
yield return WaitFor.Seconds(1);
}
}
You can start as many coroutines as you want, however, please note that with more coroutines, the slower the operating system gets.
Warning
A coroutine is not the same as a traditional C# thread, and you should not mistake the two. A C# thread is preempted; that is, if the thread encounters, for example, an infinite loop, the kernel will still continue to execute, as the thread will be automatically switched from (preempted) after a time quantum. A coroutine relies on the method to voluntarily give back control to the pool; if a software bug appears that would make the coroutine refrain from giving back control to the pool, the kernel would halt.
After performing a cycle over all coroutines, you may want to execute kernel code, to perform e.g. maintanance tasks. This can be easily achieved using the CoroutinePool.OnCoroutineCycle
delegate list:
CoroutinePool.Main.OnCoroutineCycle.Add(Main);
CoroutinePool.Main.StartPool();
// ...
void Main() {
// everything in this method will be executed after a pool cycle
}
This is a list of delegates instead of a standard C# event, as these are currently non-functional on Cosmos. See this issue for more details.
CoroutinePool
s can be set to automatically collect all unused objects on the heap after the executor finishes a cycle - that is, when all coroutines in its internal list have been ticked. This is enabled by default for the main pool, but disabled for user-created pools. It's strongly recommended to enable periodic heap collection if you're running the pool on your main thread (as is most likely the case with Cosmos).