다음을 통해 공유


Using the yield pattern with HealthVault on Windows Phone 7 (part 1)

I’ve written a Windows Phone 7 application called Weight4Me that lets you track your weight, syncing the data to HealthVault. The first version just fetched all of the weight data from HealthVault on each launch. I’m one of the developers Eric Gunnerson is referring to in this post. I’ve just posted an updated release that does a sync with HealthVault (not following exactly what Eric suggests, maybe I’ll post more details about that next). Doing sync, required doing more complex series of operations with HealthVault.

The HealthVault SDK for Windows Phone 7 provides a HealthVaultRequest class for making calls to HealthVault, calling into a callback when done. Using lambda expressions, you can make fairly readable code for simple calls as follows:

 service.BeginSendRequest(new HealthVaultRequest(methodName, methodVersion, xml,
                         (object sender, HealthVaultResponseEventArgs e) =>
{
    dispatcher.BeginInvoke(() =>
    {
        if (e.HasError)
        {
            // Handle error.
        }

        // Parse results.
    });
}));

But, I needed to do some more complex things like do multiple requests in a loop, have branching based on sync state, and so on. This turns into incredibly messy and hard to follow code with callback functions and/or lambdas. A common trick for making asynchronous code read more like synchronous code is to use the yield keyword in C# so that the compiler will build the state machine for you. Jeffrey Richter has a series of articles about using it with the standard async pattern which you can read here. I decided to apply this technique to the HealthVaultService and HealthVaultRequests, which I present here. I hope people will find it useful with HealthVault on Windows Phone 7, or will see how to apply it to similar problems.

My basic approach is to create a HealthVaultTask class, which runs a series of HealthVaultRequests. This is done by implementing an IEnumerable function that returns a series of HealthVaultOperations. HealthVaultOperation is a new class I am using to encapsulate a HealthVaultRequest and response. So, the way you implement a task looks something like:

 protected override IEnumerable<HealthVaultOperation> DoOperations()
{
    HealthVaultOperation myOperation = new HealthVaultOperation(methodName, methodVersion, xml);
    yield return myOperation;
    myOperation.ThrowOnError(); // See discussion below.
    // Parse response from myOperation.Response
}

The magic of this pattern is that you can have loops, if branches, etc in DoOperations so that the code in DoOperations looks like serial code. But, under the covers it will all be asynchronous.

There are a couple of design decisions I had to make up front. The first was about how to handle errors. There can be errors returned from the operations, and exceptions could be thrown from DoOperations itself. One approach would be to put an error code in HealthVaultOperationTask and have the caller check for the error. However, I prefer having all errors just be thrown from DoOperations method and wrapped up by the code that runs the task. To make it simple, HealthVaultOperation has a helper function called ThrowOnError() that throws an exception if there is an error returned from the HealthVault service (wrapped in a new HealthVaultException class).

The other design decision was around the threading model and how the results are communicated to the caller. I considered two basic approaches:

  1. DoOperations is always called on the UI thread and can update models/UI as necessary.
  2. DoOperations is not called on the UI thread, bundles up its results into properties on the task class, and is marshaled to the UI thread when complete.

I opted for #2. It eliminates potentially unnecessary thread context switching and keeps the code that does the network operation separated from any model/UI code. As a result, the signature of the method to run the task is as follows:

 public void Run(Dispatcher dispatcher, Action successCallback, Action<Exception> errorCallback)

When the task is run, either the successCallback will be called or the errorCallback will be called. The call will be dispatched to the UI thread. Here’s an example:

 MyHealthVaultTaskTask task = new MyHealthVaultTaskTask (service, ...);
task(dispatcher,
() => {
    // pull results off task and update model/UI.
},
(e) => {
    // Handle updating model/UI with exception e.
});

In part 2, I’ll share the code that implements HealthVaultTask.