using System.Threading.Tasks;
using System.Collections.Concurrent;
public static async Task Main()
using var dispatcher = SimpleDispatcher.StartNew();
var task1 = dispatcher.SendAsync(async () =>
Console.WriteLine($"Task 1: Thread {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"Task 1: Thread {Thread.CurrentThread.ManagedThreadId}");
var task2 = dispatcher.SendAsync(async () =>
Console.WriteLine($"Task 2: Thread {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"Task 2: Thread {Thread.CurrentThread.ManagedThreadId}");
await Task.WhenAll(task1, task2);
Console.WriteLine("All tasks complete");
public class SimpleDispatcher : IDisposable
private readonly BlockingCollection<QueueItem> queue = new();
private readonly CancellationTokenSource disposeCts = new();
private readonly Thread thread;
public SimpleDispatcher()
thread = new Thread(Run);
public static SimpleDispatcher StartNew()
var dispatcher = new SimpleDispatcher();
throw new InvalidOperationException("Already running");
public void Post(SendOrPostCallback callback, object? state)
return Task.CompletedTask;
async void RethrowExceptions()
public void Send(SendOrPostCallback callback, object? state)
return Task.CompletedTask;
item.Tcs.Task.Unwrap().GetAwaiter().GetResult();
public Task SendAsync(Func<Task> func)
return item.Tcs.Task.Unwrap();
SynchronizationContext.SetSynchronizationContext(new SimpleDispatcherSynchronizationContext(this));
var item = queue.Take(this.disposeCts.Token);
var task = item.Delegate();
item.Tcs.SetResult(task);
item.Tcs.SetException(e);
catch (OperationCanceledException) { }
public Func<Task> Delegate;
public TaskCompletionSource<Task> Tcs;
public class SimpleDispatcherSynchronizationContext : SynchronizationContext
private readonly SimpleDispatcher dispatcher;
public SimpleDispatcherSynchronizationContext(SimpleDispatcher dispatcher) => this.dispatcher = dispatcher;
public override void Send(SendOrPostCallback d, object? state)
dispatcher.Send(d, state);
public override void Post(SendOrPostCallback d, object? state)
dispatcher.Post(d, state);
public override SynchronizationContext CreateCopy() => new SimpleDispatcherSynchronizationContext(dispatcher);