3

i'm currently upgrading some existing code for use by windows universal and am struggling to convert a command pattern to work with the new async/await functionality.

I have a command scheduler class that runs within its own thread and processes commands that have been added to its queue. The method in question looks like this:

private List<ICommandItem> _items;

private void ProcessCommands()
{
    while(_items.count > 0)
    {
        _items[0].Execute();
        _items.RemoveAt(0);
    }
}

My problem is that some of my ICommandItem.Execute() methods now need to be async because they involve file io whilst others have no async methods. How could I modify the above pattern so that:

  1. my executor can handle both async and non-async ICommandItems
  2. The executor only starts execution of a command once the previous command has completed.

I would be happy to just execute the methods synchronously but that now causes deadlocks everywhere.

Daz Eddy
  • 332
  • 2
  • 15

2 Answers2

9

My problem is that some of my ICommandItem.Execute() methods now need to be async because they involve file io whilst others have no async methods.

Technically, an asynchronous (Task-returning) signature only means that the implementation may be asynchronous. So, while you can introduce a separate asynchronous interface, another approach is to change the existing interface to be Task-returning:

interface ICommandItem
{
  Task ExecuteAsync(); // Used to be "void Execute();"
}

Your synchronous implementations will have to change to return a Task:

Task ICommandItem.ExecuteAsync()
{
  // Do synchronous work.
  return Task.CompletedTask; // If you're not on 4.6 yet, you can use "Task.FromResult(0)"
}

While asynchronous implementations are straightforward:

async Task ICommandItem.ExecuteAsync()
{
  await ...; // Do asynchronous work.
}

And your "runner" can just use await:

private async Task ProcessCommandsAsync()
{
  while(_items.count > 0)
  {
    await _items[0].ExecuteAsync();
    _items.RemoveAt(0);
  }
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
0

You could provide a second interface, IAsyncCommandItem:

public interface IAsyncCommandItem : ICommandItem
{
}

In your while loop, check to see if the item implements this interface, and handle it:

private async void ProcessCommands()
{
    while(_items.count > 0)
    {
        var command = _items[0] as IAsyncCommandItem;
        if (command != null)
        {
            await command.Execute();
        }
        else
        {
            _items[0].Execute();
        }
        _items.RemoveAt(0);
    }
}

This assumes, of course, that you have the freedom to modify specific command classes to implement the new interface.

Mike Hofer
  • 16,477
  • 11
  • 74
  • 110
  • 1
    The problem with this is the return types of the two Execute methods are different (one returns a Task). I guess I could have IAsyncCommandItem and ICommandItem deriving from a common parent interface but I was hoping for a more elegant solution. – Daz Eddy Nov 18 '15 at 16:50
  • @DazEddy - both return the same thing. The await will translate the Task to the actual returned item. – Erik Funkenbusch Nov 18 '15 at 19:53