Simplifying (?) State with C# Async

I got some great feedback on my last blog post about using F#’s async feature to simplify state. One of the more interesting requests was from @FreeQaz who tweeted me:

In all honesty, my 1st reaction was “I wouldn’t do it in C#”, but then that itch started, and I started to think “How bad could it be?”. So, 1st step, port the original state to C#. This was easy and the code is here:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace CSharpXnaUsingAsync
{
 public class GameAsync : Game
 {
  GraphicsDeviceManager graphicsDeviceManager;
  SpriteBatch spriteBatch;
  float x = 0.0f, y = 0.0f, dx = 4.0f, dy = 4.0f;
  Texture2D sprite;

 public GameAsync()
 {
  graphicsDeviceManager = new GraphicsDeviceManager(this);
  Content.RootDirectory = "Content";
 }

 protected override void Initialize()
 {
  spriteBatch = new SpriteBatch(graphicsDeviceManager.GraphicsDevice);
  graphicsDeviceManager.PreferredBackBufferWidth = 640;
  graphicsDeviceManager.PreferredBackBufferHeight = 480;
  graphicsDeviceManager.ApplyChanges();
  base.Initialize();
 }

 protected override void LoadContent()
 {
  sprite = Content.Load("Sprite");
 }

 protected override void Update(GameTime gameTime)
 {
  if (x > 608.0f || x < 0.0f) dx = -dx;   if (y > 448.0f || y < 0.0f) dy = -dy;

  x = x + dx;
  y = y + dy;
 }

 protected override void Draw(GameTime gameTime)
 {
  GraphicsDevice.Clear(Color.CornflowerBlue);
  spriteBatch.Begin();
  spriteBatch.Draw(sprite, new Vector2(x, y), Color.White);
  spriteBatch.End();
 }
}

 static class Program
 {
  static void Main(string[] args)
  {
   using (GameAsync game = new GameAsync())
   {
    game.Run();
   }
  }
 }
}

Interestingly, this code doesn’t look so bad in C#. It’s not great, but it’s not awful either. In fact I bet you write C# like this all the time in your day-to-day C# life, and never think too hard about it. What was highlighted as mutable state in the F# version is quietly hidden away as is the norm in C#. Now the key F# features my last post relied on in my last post were Async workflows and the Async.AwaitEvent method. C# has async/await, but the implementation is pretty different to the F# version. And C# has no out of the box equivalent to AwaitEvent, so I’ll have to write one. Here’s how that went (full source on Github here):

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Threading.Tasks;

namespace CSharpXnaUsingAsync
{
 public class GameAsync : Game
 {
  readonly GraphicsDeviceManager graphicsDeviceManager;
  readonly TaskCompletionSource<object> initializeTCS = new TaskCompletionSource<object>();     
  readonly TaskCompletionSource<object> loadContentTCS = new TaskCompletionSource<object>(); 
  TaskCompletionSource<GameTime> updateTask = new TaskCompletionSource<GameTime>(); 
  TaskCompletionSource<GameTime> drawTask = new TaskCompletionSource<GameTime>();  
  public GameAsync()  
  {   
   graphicsDeviceManager = new GraphicsDeviceManager(this);   
   Content.RootDirectory = "Content";  
  }

  protected override void Initialize()  
  {   
   graphicsDeviceManager.PreferredBackBufferWidth = 640;   
   graphicsDeviceManager.PreferredBackBufferHeight = 480;   
   graphicsDeviceManager.ApplyChanges();   
   base.Initialize();   
   initializeTCS.SetResult(null);  
  }  

  protected override void LoadContent()  
  {   
   loadContentTCS.SetResult(null);  
  }  

  protected override void Update(GameTime gameTime) 
  {   
   updateTask.SetResult(gameTime);   
   updateTask = new TaskCompletionSource<GameTime>();  
  }  
  
  protected override void Draw(GameTime gameTime)  
  {   
   drawTask.SetResult(gameTime);   
   drawTask = new TaskCompletionSource<GameTime>();  
  }  

  public Task InitializeTask  
  {   
   get { return initializeTCS.Task; }  
  }  

  public Task LoadContentTask  
  {
   get { return loadContentTCS.Task; }  
  }  

  public Task UpdateTask  
  {
   get { return updateTask.Task; } 
  }  

  public Task DrawTask 
  {
   get { return drawTask.Task; }  
  }
 }
 static class GameWorkflow 
 {
  static async void StartWorkflow(GameAsync game)   
  {   
   await game.InitializeTask;
   var spriteBatch = new SpriteBatch(game.GraphicsDevice);  
   await game.LoadContentTask;
   var sprite = game.Content.Load<Texture2D>("Sprite"); 
   await Loop(game, spriteBatch, sprite, 0.0f, 0.0f, 4.0f, 4.0f);  
  }

  static async Task Loop(GameAsync game, SpriteBatch spriteBatch, Texture2D sprite, float x, float y, float dx, float dy)
  { 
   var updateTime = await game.UpdateTask;   
   if (x > 608.0f || x < 0.0f) dx = -dx;
   if (y > 448.0f || y < 0.0f) dy = -dy; 
   x = x + dx;
   y = y + dy; 
   var drawTime = await game.DrawTask; 
   game.GraphicsDevice.Clear(Color.CornflowerBlue); 
   spriteBatch.Begin(); 
   spriteBatch.Draw(sprite, new Vector2(x, y), Color.White);  
   spriteBatch.End();
   await Loop(game, spriteBatch, sprite, x, y, dx, dy);
  } 

  public static IDisposable RunGame()
  { 
   var game = new GameAsync(); 
   StartWorkflow(game);
   game.Run();
   return game;  
  } 
 } 
 
 static class Program 
 {  
  static void Main(string[] args)  
  {
   using (GameWorkflow.RunGame()) { };
  }
 }
}

So, this is a bit more complex than the F# version!I’ve used TaskCompletionSource to act like an event triggering in the F# version. This works really well for the “seeding” stage – Initialize and LoadContent, although the reliance on generics means I have to fudge in a type to make the compiler happy, even when I don’t *really* care about the value (note in the F# version “unit” is a real type unlike “void” in C#).In order to do the same thing for Update and Draw, I need to set the result (which I *do* care about this time – the GameTime) and then mutate the underlying TaskCompletionSource as you can only set the result once on a TaskCompletionSource.The GameWorkflow class handles all the C# async/await magic – the RunGame() method starts the async workflow using the async void antipattern. The nested async loop from the F# version has to be separated out to be able to parameterize the next frame.So, what have we lost in translation?

  1. The only things that are immutable are the 2 TaskCompletionSources and the GraphicsDeviceManager. Everything else is still mutable, and we cant change that due to C#.
  2. Because we had to break out the async loop into its own function it no longer captures the “seed” state from the initialization. This means if we add in more things (ie more sprites or models) we either have to encapsulate those in some class to pass in or keep adding parameters to the Loop method.
  3. The state hasn’t been simplified, and in fact this solution is almost twice as long as the original!

The previous post took something that would have been idiomatic C# (mutation based approach) and transformed it into what would look more like an idiomatic F# approach – constrained mutations. By the end of that post we had something that looked like natural F#.Ironically, taking the default idiomatic C# solution and applying the pattern from F# yielded something quite ugly, completely non-idiomatic, and with little of the benefit of the F# version (for example there is hardly any less mutation, and we’ve had to alter the control flow significantly from the F# version).By the time I had finished the 2nd iteration over this code it didn’t seem wise to apply the 3rd pass from the previous post. Feel free to send a pull request if you think it would be beneficial!

Conclusion

We now live in a polyglot world. Java and Scala play nicely on the JVM. Java and Clojure too. C# and F# are complementary tools on the .NET framework, and using a best of breed approach to development tools is probably the right choice. In the above example, we can see where F# shines compared to C# in terms of the async programming model. However there are areas where you might want to choose C#, or are not presented F# as an option. In those cases it may be advisable to stick to the idiomatic path, unless you can reap some greater rewards by straying from it.

Note: I’m tempted to do an RX version, just too see if that works any better from a C# language perspective. Let me know if you’re interested!

About thedo666

Software developer trying to learn a new language - English!
This entry was posted in Uncategorized. Bookmark the permalink.

1 Response to Simplifying (?) State with C# Async

  1. Pingback: F# Weekly #21, 2015 | Sergey Tihon's Blog

Leave a comment