Synchronizing UI Operations with Asynchronous Dependencies
When working with asynchronous data access such as using a Silverlight client to access data from a server side service, you will inevitably run into the situation where you have two or more calls which have results that need to be processed in a synchronous manner. A simple example is populating a list box from server side data and then making a second call to get the value that needs to be selected. You could wait until the list is populated before making the second call, but that would be a waste.
I built a little helper object that allows you to define Action delegates that will get executed in a specific order once all pending operations have completed. Below is a snippet of how it can be used.
private ILoginService loginService; private ILoginView loginView; private OperationSync localeOpSync = new OperationSync(LocaleOps.GetLocales, LocaleOps.GetDefaultLocale);private void PopulateLocales() { loginService.GetLocales(HandleGetLocalesResult); loginService.GetDefaultLocale(HandleGetDefaultLocaleResult); }
private void HandleGetDefaultLocaleResult(string result) { localeOpSync.OpCompleted(LocaleOps.GetDefaultLocale, GetSetSelectedLocaleAction(result)); }
private void HandleGetLocalesResult(string[] result) { localeOpSync.OpCompleted(LocaleOps.GetLocales, GetSetLocaleSelectionAction(result)); }
private Action GetSetSelectedLocaleAction(string result) { return () => loginView.SetSelectedLocale(result); }
private Action GetSetLocaleSelectionAction(string[] result) { return ()=> loginView.SetLocaleSelections(result); }
To show how it works I’ve included a subset of the unit tests (with extension methods and other goodies stripped out). The setup for all the specs is at the bottom of the code snippet.
[TestFixture] public class when_not_completing_all_ops : behaves_like_setting_up_operation_actions { [Test] public void should_not_execute_actions() { OpSync.OpCompleted(OpKey.One, Action1); OpSync.OpCompleted(OpKey.Two, Action2);CollectionAssert.IsEmpty(ActionResults); }}
[TestFixture] public class when_completing_all_ops_in_key_order : behaves_like_setting_up_operation_actions { [Test] public void should_execute_actions_in_key_order() { OpSync.OpCompleted(OpKey.One, Action1); OpSync.OpCompleted(OpKey.Two, Action2); OpSync.OpCompleted(OpKey.Three, Action3);
Assert_That_ActionResults_Fire_In_Correct_Order(); }}
[TestFixture] public class when_completing_all_ops_not_in_key_order : behaves_like_setting_up_operation_actions { [Test] public void should_execute_actions_in_key_order() { OpSync.OpCompleted(OpKey.Two, Action2); OpSync.OpCompleted(OpKey.Three, Action3); OpSync.OpCompleted(OpKey.One, Action1);
Assert_That_ActionResults_Fire_In_Correct_Order(); }}
public abstract class behaves_like_setting_up_operation_actions { protected OperationSync OpSync { get; set; } protected Action Action1 { get; set; } protected Action Action2 { get; set; } protected Action Action3 { get; set; } protected List<OpKey> ActionResults { get; set; }
[SetUp] <span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> Before_Each_Spec() { OpSync = <span style="color: #0000ff">new</span> OperationSync(OpKey.One, OpKey.Two, OpKey.Three); Action1 = () => ActionResults.Add(OpKey.One); Action2 = () => ActionResults.Add(OpKey.Two); Action3 = () => ActionResults.Add(OpKey.Three); ActionResults = <span style="color: #0000ff">new</span> List<OpKey>(); } <span style="color: #0000ff">protected</span> <span style="color: #0000ff">void</span> Assert_That_ActionResults_Fire_In_Correct_Order() { Assert.That(ActionResults.Count, Is.EqualTo(3)); Assert.That(ActionResults[0], Is.EqualTo(OpKey.One)); Assert.That(ActionResults[1], Is.EqualTo(OpKey.Two)); Assert.That(ActionResults[2], Is.EqualTo(OpKey.Three)); } <span style="color: #0000ff">protected</span> <span style="color: #0000ff">enum</span> OpKey { One, Two, Three }}
The keys can be anything. I have just been creating a private enum in the class where I need them. Here’s the OperationSync class that handles it all:
public class OperationSync { private static readonly object syncRoot = new object();<span style="color: #0000ff">private</span> <span style="color: #0000ff">readonly</span> <span style="color: #0000ff">object</span>[] keys; <span style="color: #0000ff">private</span> <span style="color: #0000ff">readonly</span> Dictionary<<span style="color: #0000ff">object</span>, <span style="color: #0000ff">bool</span>> keyKeepActionAfterExecute = <span style="color: #0000ff">new</span> Dictionary<<span style="color: #0000ff">object</span>, <span style="color: #0000ff">bool</span>>(); <span style="color: #0000ff">private</span> <span style="color: #0000ff">readonly</span> Dictionary<<span style="color: #0000ff">object</span>, Action> keyActions = <span style="color: #0000ff">new</span> Dictionary<<span style="color: #0000ff">object</span>, Action>(); <span style="color: #0000ff">public</span> OperationSync(<span style="color: #0000ff">params</span> <span style="color: #0000ff">object</span>[] operationKeys) { keys = operationKeys; <span style="color: #0000ff">foreach</span> (var o <span style="color: #0000ff">in</span> operationKeys) { keyActions.Add(o, <span style="color: #0000ff">null</span>); } } <span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> OpCompleted(<span style="color: #0000ff">object</span> key, Action action) { OpCompleted(key, action, <span style="color: #0000ff">false</span>); } <span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> OpCompleted(<span style="color: #0000ff">object</span> key, Action action, <span style="color: #0000ff">bool</span> keepActionAfterExecute) { <span style="color: #0000ff">lock</span> (syncRoot) { <span style="color: #0000ff">if</span> (!keyActions.ContainsKey(key)) <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> UIException(<span style="color: #006080">"Key '{0}' not found."</span>); keyActions[key] = action; keyKeepActionAfterExecute[key] = keepActionAfterExecute; <span style="color: #0000ff">if</span> (AllOpsComplete()) ProcessActions(); } } <span style="color: #0000ff">private</span> <span style="color: #0000ff">void</span> ProcessActions() { ExecuteEachAction(); ClearActionsIfIndicated(); } <span style="color: #0000ff">private</span> <span style="color: #0000ff">void</span> ExecuteEachAction() { <span style="color: #0000ff">foreach</span> (var key <span style="color: #0000ff">in</span> keys) { keyActions[key](); } } <span style="color: #0000ff">private</span> <span style="color: #0000ff">void</span> ClearActionsIfIndicated() { <span style="color: #0000ff">foreach</span> (var key <span style="color: #0000ff">in</span> keys) { <span style="color: #0000ff">if</span> (!keyKeepActionAfterExecute[key]) keyActions[key] = <span style="color: #0000ff">null</span>; } } <span style="color: #0000ff">private</span> <span style="color: #0000ff">bool</span> AllOpsComplete() { <span style="color: #0000ff">return</span> keyActions.All(pair => pair.Value != <span style="color: #0000ff">null</span>); }}
There’s probably a fancy smancy AJAX pattern name for this sort of thing. Let me know if you come across one.
