Tuesday, December 11, 2012

OperationContextScope and async/await

So, we're doing a Windows 8 app. Yay :)
Being a c# and Silverlight kinda guy, the time needed to get up and running was incredibly small. As always, Microsoft are very good on the Tool-side of things and the "F5" experience works very well in the Windows 8 area.

Our services are WCF based, and we support both REST and SOAP.
In this case, we decided to use the SOAP way since our REST client helper library did not exist for Win8.
"Add New Service Reference" and boom we got ourselves some client code.

To make this work though, we had to put some time into the connectivity/authorization part.
We use a cookie to store authentication token (you know, standard ASP.NET authorization) and we needed  to pass along that cookie with other calls. We found that the best way to do so was to use the OperationContextScope and a previously saved cookiecontainer from the authorization call.


// prepares a soap client with the stored cookie container
var docClient = CreateDocumentServiceClient();

// call webservice
using (new OperationContextScope(docClient.InnerChannel))
{
   var doc = docClient.GetDocumentAsync(docId);
}


(...not gonna go into details of how to store a cookiecontainer etc, but I'm sure your friend Google could aid you if needed.)

In many of our calls, we needed to check something after the async call, so we started using await


private async void DoStuffWithDoc()
{
  var docClient = CreateDocumentServiceClient();  
  using (new OperationContextScope(docClient.InnerChannel))
  {
     await var doc = docClient.GetDocumentAsync(docId);
     if (doc.YadaYada)
     {
          // more code here
     }
  }
}


This mostly worked fine, but after a while we started noticing Exceptions, especially when doing other things in parallel. Often, it was "This OperationContextScope is being disposed on a different thread than it was created". When looking at the documentation for OperationContextScope it states that:
Caution noteCaution
Do not use the asynchronous “await” pattern within a OperationContextScope block. When the continuation occurs, it may run on a different thread and OperationContextScope is thread specific. If you need to call “await” for an async call, use it outside of the OperationContextScope block


Aha! So, how do you solve this?
The await keyword will split up your code and basically create a new method of all the code after the await keyword (and that "new method" will be executed in a separate thread sometimes). Because the end of the using statement is after the await keyword, the Dispose of the OperationContextScope instance will take place in the "new method" that might run in a separate thread.

The solution is very easy, just make sure you do not use await inside a OperationContextScope. Duh!

So, all of our methods doing any kind of webservice call with an OperationContextScope needed to be refactored looking something like this:


private async void DoStuffWithDoc(string docId)
{
   var doc = await GetDocumentAsync(docId);
   if (doc.YadaYada)
   {
        // more code here
   }
}

public Task<Document> GetDocumentAsync(string docId)
{
  var docClient = CreateDocumentServiceClient();
  using (new OperationContextScope(docClient.InnerChannel))
  {
    return docClient.GetDocumentAsync(docId);
  }
}


See what we did there?
The await keyword has been moved outside the OperationContextScope to the calling method instead, and we immediately return after the call using the OperationContextScope.

Problem solved!

26 comments:

  1. This was awesome! After hours of googling, I'm happy to have found your post. Solved my problem quite exactly - thank you so much!
    -Eric

    ReplyDelete
  2. Thanks! It worked :)

    ReplyDelete
  3. What if you need to capture some headers in the Incoming response and return them to the caller method? How do you handle that?

    ReplyDelete
    Replies
    1. Have not had the need to do that, but you could probably either use an out argument to the function, and/or use a return value that has some parts filled in directly after the using scope and some parts filled in by the sub function (in my case GetDocumentAsync)

      Delete
  4. i noticed your one method is 'async void' which is a no-no unless it is an async event. All async methods should at least return Task (if void return). it should be changed to:

    private async Task DoStuffWithDoc(string docId)

    ReplyDelete