Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C#

IDispatch

Rate me:
Please Sign up or sign in to vote.
4.82/5 (9 votes)
22 Aug 2009CPOL4 min read 36.8K   309   31   4
Pattern to easily create and test asynchronous solutions.

Sample Image

Problem

How can I make two classes interact between themselves asynchronously without making my code harder to write, understand, run, and test?

Solution

Use a mediator between these two objects: a dispatcher; this dispatcher will be asynchronous in the production environment and synchronous in the test environment, making it easier to test and debug. It's a free lock solution!

Real life story

In my country (France), I've been used to going to the post office and waiting thirty minutes to send a packet. I'm not very patient waiting in the queue, away from my computer, it is like hell. Fortunately, some years ago, the post service improved. Now, you just have to tell what you want when you enter in the post office, then someone gives me a ticket with a number. Now, I can sit down, and write an article while I'm waiting for my turn.

So what's the big deal?

Let's translate this real world example to technical terms. I'm a client, and I enqueue a task which describes what I want the server to do in the dispatcher. The dispatcher forwards one task after another to the server. When my task is in the server's hands, it processes it and maybe, it will need my intervention. So, it will call me, and we will initialize a conversation to complete the task.

Actors

  • Client
  • Server
  • Dispatcher
  • Task
  • Conversation

The point is that the client and the server don't need to be thread safe to communicate; the client is aware of the dispatcher, not the server.

So first, a dispatcher is an object which takes an Action and doesn't give any guarantee on when the action will be executed:

Image 2

I'll talk about the classes later; quickly, WPFDispatcher encapsulates a System.Threading.Dispatcher object (from WPF, WindowBase.dll), as ThreadDispatcher does, except that it also creates a new thread.

Example

Here is the class design for my example:

Image 3

PersonsViewModel is the client of IPersonRepository which is an interface representing the server. Only one class implements IPersonRepository: VerySlowPersonRepository. In a real life scenario, you can imagine creating a SqlPersonRepository. VerySlowPersonRepository will never return the same persons (it's for simulating that data is shared).

C#
public class VerySlowPersonRepository : IPersonRepository
{
    string[] _Persons = new string[] { "Micky", "Pams", 
       "Kat", "Nick", "Tom", 
       "James", "Gil", "Joe" };
    Random _Rand = new Random();

    #region IPersonRepository Members

    public string[] GetPersonsName()
    {
        Thread.Sleep(3000);
        return GetRandomPersons(5);
    }

    private string[] GetRandomPersons(int number)
    {
        if(number > _Persons.Length)
            number = _Persons.Length;
        List<int> chosenPersons = new List<int>();
        while(chosenPersons.Count < number)
        {
            int takenPerson = _Rand.Next(_Persons.Length);
            if(!chosenPersons.Contains(takenPerson))
                chosenPersons.Add(takenPerson);
        }
        return chosenPersons.Select(i => _Persons[i]).ToArray();
    }

    #endregion
}

A task is only a delegate passed by the client to the dispatcher:

C#
protected void DoRefresh()
{
    IsLoading = true;
    _Repository.BeginInvoke((IPersonRepository repo) =>
    {
        ...
    });
}

The client can have its own dispatcher. By design, a dispatcher is thread safe, so a conversation can be initiated inside the task between the client and the server. Here is an example of a conversation, taken from the class PersonsViewModel. A conversation is a cascading tasks exchange between a client and a server.

C#
protected void DoRefresh()
{
    IsLoading = true;
    _Repository.BeginInvoke((IPersonRepository repo) =>
    {
        var persons = repo.GetPersonsName();
        _CurrentDispatcher.BeginInvoke(() =>
        {
            Persons.Clear();
            foreach(var person in persons)
            {
                Persons.Add(person);
            }
            IsLoading = false;
        });
    });
}

Before going further, you may have noticed that there is a generic and a non generic dispatcher. In reality, the generic version of IDispatcher is only a dispatcher attached to a server; to create your own dispatcher, you should only implement the non-generic version. Every task has access to the server, thanks to the generic version of the dispatcher (the server's type is the type parameter of IDispatcher).

Image 4

This is an example of how I inject dispatchers in my client (given that the client will run in the current WPF dispatcher, and the server is on another thread). Note the use of the method extension Attach. There are two overloads to the method: one which takes the server as a parameter, and the other which takes a Func<server> delegate, which will create the server in the thread of the dispatcher.

My client constructor:

C#
public PersonsViewModel(IDispatcher<ipersonrepository> repository, 
                        IDispatcher currentDispatcher)
{
    _Persons = new ObservableCollection<string>();
    _Repository = repository;
    _CurrentDispatcher = currentDispatcher ?? new WpfDispatcher();
    DoRefresh();
}

Then the injection:

C#
private void Button_Click(object sender, RoutedEventArgs e)
{
    viewModel.ViewModel = CreateViewModel();
}

private PersonsViewModel CreateViewModel()
{
    var currentDisp = new WpfDispatcher();
    var otherDisp = new ThreadDispatcher().Attach<ipersonrepository>(
                                   new VerySlowPersonRepository());
    return new PersonsViewModel(otherDisp, currentDisp);
}

Here is the implementation of WPFDispatcher and ThreadDispatcher; the only important thing to note is that they use the System.Threading.Dispatcher class of WindowBase.dll.

WPFDispatcher:

C#
public class WpfDispatcher : IDispatcher
{
    private Dispatcher _disp;
    public WpfDispatcher(Dispatcher disp)
    {
        _disp = disp ?? Dispatcher.CurrentDispatcher;
        Priority = DispatcherPriority.Normal;
    }

    public WpfDispatcher()
        : this(null)
    {

    }

    public DispatcherPriority Priority
    {
        get;
        set;
    }
    public void BeginInvoke(Action action)
    {
        _disp.BeginInvoke(action, Priority, null);
    }
}

ThreadDispatcher:

C#
public class ThreadDispatcher : IDispatcher, IDisposable
{
    Thread _Thread;
    Dispatcher _DispThread;
    private Dispatcher Disp
    {
        get
        {
            if(_DispThread == null)
            {
                _DispThread = Dispatcher.FromThread(_Thread);
                if(_DispThread == null)
                {
                    Thread.Sleep(100);
                    return Disp;
                }
            }
            return _DispThread;
        }
    }

    public ThreadDispatcher()
    {
        _Thread = new Thread(() =>
        {
            Dispatcher.CurrentDispatcher.UnhandledException += 
              new DispatcherUnhandledExceptionEventHandler(
              CurrentDispatcher_UnhandledException);
            Dispatcher.Run();
        });
        _Thread.Start();
    }

    void CurrentDispatcher_UnhandledException(object sender, 
                           DispatcherUnhandledExceptionEventArgs e)
    {
        e.Handled = true;
    }
    public void BeginInvoke(Action action)
    {
        Disp.BeginInvoke(action);
    }

    public void Dispose()
    {
        if(Disp != null)
            Disp.BeginInvokeShutdown(DispatcherPriority.Normal);
    }
}

Results

For those who dream of WPF, you have noticed that my client's name is not a random choice. So now, let's see my ViewModel in action in my real life WPF application:

Loading...

Image 5

Persons:

Image 6

Testability

I said that it was easy to test, so here I go, this is the test of the client:

C#
public class FakePersonRepository : IPersonRepository
{
    #region IPersonRepository Members

    public string[] GetPersonsName()
    {
        return new String[] { "Mike", "Tom", "Joe" };
    }

    #endregion
}

[TestMethod]
public void DispatchViewModelTest()
{
    SynchronizedDispatcher dispatcher = new SynchronizedDispatcher();
    var repository = dispatcher.Attach<ipersonrepository>(new FakePersonRepository());
    PersonsViewModel vm = new PersonsViewModel(repository, dispatcher);
    Assert.AreEqual(3, vm.Persons.Count);
}

Not very hard to read and to write, is it?

Warning: SynchronizedDispatcher should only be used for testing purposes, because all tasks sent to a dispatcher must run on the same thread. If the client and the server both don't have a SynchronizedDispatcher, this contract breaks.

Miscellaneous

If you want to convert a IDispatcher<derivedclass> to an IDispatcher<baseclass>:

C#
var dispatcherBase = IDispatcherExtensions.Cast<derived,>(dispatcher);

Conclusion

I don't know if you have already tried to unit test an asynchronous communication between two objects; if you've, now you certainly have more white hairs than brown ones. Now you can easily test the interaction between two objects which can be in different threads.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Freelance
France France
I am currently the CTO of Metaco, we are leveraging the Bitcoin Blockchain for delivering financial services.

I also developed a tool to make IaaS on Azure more easy to use IaaS Management Studio.

If you want to contact me, go this way Smile | :)

Comments and Discussions

 
GeneralExcellent Pin
Daniel Vaughan23-Aug-09 0:22
Daniel Vaughan23-Aug-09 0:22 
GeneralRe: Excellent Pin
Nicolas Dorier23-Aug-09 2:59
professionalNicolas Dorier23-Aug-09 2:59 
GeneralRe: Excellent Pin
Daniel Vaughan23-Aug-09 3:24
Daniel Vaughan23-Aug-09 3:24 
GeneralRe: Excellent Pin
Nicolas Dorier23-Aug-09 4:04
professionalNicolas Dorier23-Aug-09 4:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.