https://github.com/tosh-coding/AsyncFiberWorks
This is a fiber-based C# threading library. The goal is to make it easy to combine fiber and asynchronous methods.
Forked from Retlang. I’m refactoring it to suit my personal taste. I use it for my hobby game development.
This is still in the process of major design changes.
“Task.Run” uses a shared thread pool in the background. If I/O wait processing is performed synchronously there, other tasks will get stuck, causing performance degradation. This can be avoided by using a separate thread instead.
async Task SampleAsync()
{
// Ready-made another thread pool is available.
await AnotherThreadPool.Instance.CreateFiber().EnqueueAsync(() =>
{
// It calls a blocking function, but it doesn't affect .NET ThreadPool.
// Because it's on an another thread.
SomeBlockingFunction();
});
...
}
To “Fire and forget” multiple tasks in parallel, create an another thread pool.
UserThreadPool userThreadPool = UserThreadPool.StartNew(4);
...
userThreadPool.Queue((x) => SomeReadWriteSyncAction());
...
userThreadPool.Dispose();
Often in game libraries, there are functions that can only be called in the main thread. By treating the main thread as a task queue loop, they can be used on asynchronous contexts.
class Program
{
static void Main(string[] args)
{
// Create a task queue.
var mainThreadLoop = new ThreadPoolAdapter();
// Starts an asynchronous operation. Pass the task queue.
RunAsync(mainThreadLoop);
// Consume tasks taken from that queue, on the main thread.
// It will not return until the task queue is stopped.
mainThreadLoop.Run();
}
static async void RunAsync(ThreadPoolAdapter mainThreadLoop)
{
...
// Enqueue actions to the main thread loop.
mainThreadLoop.Enqueue(() => someAction());
mainThreadLoop.Enqueue(() => someAction());
...
// Stop the task queue loop of the main thread .
mainThreadLoop.Stop();
}
A task queue loop running on a thread pool does not guarantee the order in which tasks are executed. Fiber can be used to guarantee the order.
// Create a fiber that runs on the default `.NET ThreadPool`.
var fiber = new PoolFiber();
// Enqueue actions via fiber to guarantee execution order.
int counter = 0;
fiber.Enqueue(() => counter += 1);
fiber.EnqueueTask(async () =>
{
await Task.Delay(1000);
counter *= 100;
});
fiber.Enqueue(() => counter += 2);
fiber.Enqueue(() => Assert.AreEquals(102, counter));
Can wait for fiber processing completion in an asynchronous context.
async Task SomeMethodAsync()
{
var anotherThreadFiber = AnotherThreadPool.Instance.CreateFiber();
anotherThreadFiber.Enqueue(() => someA());
anotherThreadFiber.Enqueue(() => someB());
anotherThreadFiber.Enqueue(() => someC());
// Wait for queued actions to complete.
await anotherThreadFiber.EnqueueAsync(() => {});
...
}
Running on a shared thread:
Runs on a newly created dedicated thread:
Runs on a dedicated specific thread:
Runs by manually pumping tasks:
See API Documentation here: https://tosh-coding.github.io/AsyncFiberWorks/api/
Unit tests can also be used as a code sample.
Fiber is a mechanism for sequential processing. Actions added to a fiber are executed sequentially. Action and Func<Task> can be added.
Related operations can be submitted to a single fiber to run sequentially, while unrelated operations can be submitted to different fibers to run in parallel. When dealing with multiple fibers, they can be thought of as actors. This design concept has not changed significantly from the source Retlang. The following description is taken from Retlang.
Message based concurrency in .NET […] The library is intended for use in message based concurrency similar to event based actors in Scala. The library does not provide remote messaging capabilities. It is designed specifically for high performance in-memory messaging.
(Quote from Retlang page. Broken links were replaced.)
Producer-Consumer pattern. One or more threads become consumers and execute tasks taken from the task queue.
These are mechanisms for loosely coupling messaging within a process.
A design that specifies a destination Fiber and sends messages directly results in tight coupling. This is not a problem if the design is small or speed is the top priority. However, as the design scale increases, the disadvantages often outweigh the benefits. In such cases, this mechanism can be used.
By replacing existing messaging code with code that uses these Pub/Sub interfaces, you can write sending code without specifying a destination. While the dependency on the Pub/Sub interface remains, messaging is one level more loosely coupled than before.
These are mechanisms for sequential processing when using multiple fibers. Call all tasks in the order in which they were registered. Wait for the calls to complete one by one before proceeding. Different fibers can be specified for each action. Can be performed repeatedly.
FiberAndTaskPairList.CreateWaiter(). Example.FiberAndHandlerPairList<TMessage>.CreateWaiter(). Example.| Execution context | Pause method |
|---|---|
| Dedicated thread | Thread.Sleep() |
| Fiber on shared threads | fiber.Enqueue(Action<FiberExecutionEventArgs>) & FiberExecutionEventArgs.Pause()/Resume() |
| Asynchronous control flow | await Task.Deley() |