C# 5.0 & Rx Schedulers

It’s been a pretty exciting week in the C# world. I initially had mixed feelings.

1.    I think this will really help Silverlight developers needing to make network IO calls. If you didn’t realise all network IO in Silverlight uses the Asynchronous Programming Model.
2.    However at the same time there is all this negative, “Silverlight is dead hype”.
3.    I’d really prefer an ITask interface. I’m not a fan of baking Task into my class interfaces. It means I’ll need special testing infrastructure to mock these things.

The obvious use case is we can now perform some network operation, and continue running our code when it’s done. Great useful feature but nothing we couldn’t already do. The excitement died pretty quickly for me on this one.

I’m now exploring this stuff a little more, and I’ve stumbled across an interesting use pattern that was not immediately obvious to me.
With a few simple extension methods, we can now write code like this!

static async void RunWorker()
{
    await Scheduler.ThreadPool.SwitchTo(); 
    // do some work 
    await Scheduler.Dispatcher.SwitchTo(); 
    // update the ui 
    await Scheduler.NewThread.SwitchTo(); 
    // perform blocking operation 
    await Scheduler.TaskPool.SwitchTo(); 
    // crunch some numbers 
    await Scheduler.Dispatcher.SwitchTo(); 
    // display some results & return
}

 

Let me explain…

We require an extension method for IScheduler. This extension method will return a scheduler task, allowing the await keyword to place a callback on the specified scheduler. That task will look like this.

public interface ITask
{
    IAwaiter GetAwaiter();
}

public interface IAwaiter
{
    bool BeginAwait(Action callback);
    void EndAwait();
}

 

Extension method signature will look like this.

public static ITask SwitchTo(this IScheduler scheduler)

I think it will also be useful to have an overload that yields control for the current thread and calls back after a given time interval.

public static ITask SwitchTo(this IScheduler scheduler, IScheduler interval)

 

Here is the implementation.

public static class SchedulerEx
{
    public static ITask SwitchTo(this IScheduler scheduler)
    {
        return new AnonymousTask(() => new ScheduledAwaiter(callback => scheduler.Schedule(callback)));
    }

    public static ITask SwitchTo(this IScheduler scheduler, TimeSpan interval)
    {
        return new AnonymousTask(() => new ScheduledAwaiter(callback => scheduler.Schedule(callback, interval)));
    }

    private class AnonymousTask : ITask
    {
        private readonly Func<IAwaiter> _getAwaiter;

        public AnonymousTask(Func<IAwaiter> getAwaiter)
        {
            _getAwaiter = getAwaiter;
        }

        public IAwaiter GetAwaiter()
        {
            return _getAwaiter();
        }
    }

    private class ScheduledAwaiter : IAwaiter
    {
        private readonly Action<Action> _scheduleCallback;

        internal ScheduledAwaiter(Action<Action> scheduleCallback)
        {
            _scheduleCallback = scheduleCallback;
        }

        public bool BeginAwait(Action callback)
        {
            _scheduleCallback(callback);
            return true;
        }

        public void EndAwait()
        {
        }
    }
}

 

A simple test application;

class Program
{
    static EventLoopScheduler _kitchen = new EventLoopScheduler("Kitchen Thread");
    static EventLoopScheduler _diningRoom = new EventLoopScheduler("Dining Room");

    static void Main()
    {
        Enumerable.Range(1, 5).Run(CreateWaiter);
        Console.ReadLine();
    }

    static async void CreateWaiter(int id)
    {
        while (true)
        {
            await _kitchen.SwitchTo();

            Console.WriteLine("{0}: {1} (I should be in the kitchen)", id, Thread.CurrentThread.Name);

            await _diningRoom.SwitchTo();

            Console.WriteLine("{0}: {1} (I should be in the dining room)", id, Thread.CurrentThread.Name);

            await Scheduler.TaskPool.SwitchTo();

            Console.WriteLine("{0}: {1} (I should be in the pool)", id, Thread.CurrentThread.Name == null ? "Pool" : Thread.CurrentThread.Name);
        }
    }
}

 

I’d imagine this will become quite a popular pattern. It looks like they are planning to add a method just like this to the ThreadPool. Perhaps it will become the norm on all scheduling constructs.

Going to sleep on it – More soon!

Follow

Get every new post delivered to your Inbox.