Async and await in Contentful's .NET SDK

Droping box1

Today I'm pleased to announce the pre-release of our official .NET SDK! It's long overdue, but we've finally managed to get around to creating an officially supported SDK for all the .NET developers out there. As a token of appreciation, we're happy to announce that the .NET SDK also fully supports our Content Management API (CMA). This means that not only can you use it for delivering content to all your devices, but it also allows you to make edits and update content on the fly!

The SDK is built on top of the Microsoft .NET Standard Library 1.4 and .NET core. This means it's fully compatible with .NET core, .NET Framework 4.6.1 or later, Mono/Xamarin and Universal Windows Platform. Furthermore as it is built on top of .NET Core it is cross platform and can be used interchangeably on Windows, Linux or OSX. The SDK is available on NuGet Install-Package contentful.csharp -Pre and is built with a modern, dependency injection friendly approach. It lets you, as a .NET developer, use your own classes to model your content. It also leverages asynchronous programming heavily which brings us to our next point…

Async programming

The concept of asynchronous programming can at times seem arcane and mysterious; in fact, it's hard to reason about code when you don't know exactly in which order specific lines of code run. In .NET the whole process got a whole lot easier with the introduction of the async and await keywords a couple of years back (a feature that is about to come to JavaScript as well eventually). And although it's been a while since they were introduced, many .NET developers still haven't had a chance to use them and can feel put off by all the async methods in the Contentful .NET SDK. The aim of the rest of this article is to give you a better understanding of how asynchronous programming works in .NET and the benefits it brings to your code.

Why async?

Why even bother? Why can't everything just run synchronously? The answer is of course that everything could run synchronously and the code would still work as expected, but let's look at this small example.

1
2
3
var product = _client.GetEntry<Product>("123");
    
return product.Name;

This method is not asynchronous and thus runs synchronously. The call to GetEntry in turn calls methods on the HttpClient and fetches a response from the Contentful API. This means that while we're waiting for the API response (quick as it might be there's still a significant latency in code execution terms) no other code can run on the thread. It is busy doing nothing but waiting for the response from the Contentful API. We could compare it to if you stand in line to order a burger and the cashier tells you your order will be done in a minute, and then just stands there staring at you for a full minute until your burger is ready. That's of course extremely inefficient. There must be a better way to use that minute of wasted time. This is basically what we address when we add async and await to the mix. Let's rewrite our method slightly.

1
2
3
var product = await _client.GetEntryAsync<Product>("123");

return product.Name;

The addition of await makes all the difference in the world. The code will now wait asynchronously for the result of GetEntryAsync meaning that while it's waiting, the thread is free to do whatever other work it needs to do. It's like the cashier in our example above telling you that your order will be done in a minute, and then going on to serve the next person in line.

Note that this might not get you your hamburger faster, but it is a more efficient use of resources. The same thing is true for our GetEntry call, making it asynchronously will not make the call return the result any faster, but makes sure we use our resources as efficiently as possible.

Can we await any method?

Unfortunately (or maybe fortunately) no, to be able to await a method the result from that method needs to be awaitable. What does this mean? Well, it's quite complex, but it basically means the return type of the method needs to be able to capture an execution context and all sorts of very low-level things which are well beyond the scope of this article. Fortunately, there are two simple awaitables already created and ready to be used in the .NET framework. Task and Task<T>. If a method returns any of these two types, it can be awaited.

This leads us to an interesting conclusion. There must be a difference between the return type of GetEntry and GetEntryAsync.

1
2
3
public T GetEntry<T>;

public Task<T> GetEntryAsync<T>;

If you try to call the GetEntryAsync method without using await you'll end up with a Task<T> and not the Product class as you might've hoped. By placing the await keyword before our method call the result will be the unwrapped result of Task<T> to a T or Product in our example.

But then my method must also be async…

If you have a method like this:

1
2
3
4
5
public Product GetProductById(string id) {
  var product = await _client.GetEntryAsync<Product>(id);

  return product;
}

You'll notice that this code does not compile. The error message is The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task<Product>'. We have one more requirement to be able to use the await operator: the method itself must itself be asynchronous. Thus we must rewrite our method like this.

1
2
3
4
5
public async Task<Product> GetProductById(string id) {
  var product = await _client.GetEntryAsync<Product>(id);

  return product;
}

The code will now compile just fine. We should also add the async suffix to our method to adhere to common practice.

1
2
3
4
5
public async Task<Product> GetProductByIdAsync(string id) {
  var product = await _client.GetEntryAsync<Product>(id);

  return product;
}

This is all great, but it also means that any code calling our GetProductByIdAsync method also needs to be asynchronous, at least to be able to use the await operator (which you should).

Some people have referred to the async/await pattern as an infection that slowly consumes your entire code base. Although of course it's an exaggeration, introducing asynchronous methods does tend to force you to change more than just the calling method into an asynchronous one. While this can be tedious, the upside is that you are, with a few simple keywords, making sure your code is as performant and non-blocking as possible.

Deadlocks ahoy!

While the async/await keywords make asynchronous programming almost invisible to us, they also do increase the complexity under the hood. Let's again look at a simple example to better illustrate what we're talking about.

1
2
3
4
5
6
7
public async Task SomeAsyncMethod() {
  var i = 5 +7;

  await DoSomethingWithSomeNumberAsync(i);

  i += 36;
}

This somewhat useless function runs asynchronously, but is all of it asynchronous? In fact no, the code will run synchronously until it reaches the await operator. Now, await grabs the awaitable returned from the DoSomethingWithSomeNumberAsync method and examines if it has already completed. If so it then just resumes code execution normally and synchronously. If it has not completed, however, it returns and exits the current method and tells the awaitable to execute the remainder of the method once it completes. This means that i += 36; will not be executed until the DoSomethingWithSomeNumberAsync is completed, thus giving us the illusion of synchronous code while being run asynchronously instead.

We could force our method to block while waiting for the asynchronous method. Let's look at an example in a web context. Imagine we have the following controller action.

1
2
3
4
5
6
public IActionResult ShowProduct(string id) {

  var product = _client.GetEntryAsync<Product>(id).Result;

  return View(product);
}

Here we've gotten lazy and haven't made our method async, instead we block the method by calling .Result on our awaitable, effectively making our asynchronous calls useless as the thread is now being blocked waiting for the result of GetEntryAsync to return. Not only does this make our call synchronous and our async calls further down the call chain superfluous, but it can also be a recipe for deadlocks.

Every async call in .NET is by default trying to keep track of the context in which it was called. In our Asp.NET example above this would be the RequestContext of the current request. When the async method returns, it tries to get ahold of this context again to make sure the remainder of the code gets executed in the same context as for where the method entered. This poses a real problem in the above example: when the async method tries to get ahold of the RequestContext, it's already busy waiting for the Result of the asynchronous method, which in turn is waiting for the RequestContext to become available. Deadlock!

Luckily the Contentful SDK mitigates this problem by disregarding the context in which it was called. It simply isn't necessary for the SDK to return the result of the calls in any specific context. This means that the code above actually does work when using the Contentful .Net SDK directly, but it's still not a good practice. Here's a better way to write that same method.

1
2
3
4
5
6
public async Task<IActionResult> ShowProduct(string id) {

  var product = await _client.GetEntryAsync<Product>(id);

  return View(product);
}

Non-blocking asynchronous code and no risk of deadlocks.

Why not expose synchronous calls as well?

Of course, we could double the API surface of IContentfulClient and IContentfulManagementClient by adding a blocking, synchronous method for each call. We would then have two methods for every call to the API, for example.

1
2
public T GetEntry<T>(string id);
public Task<T> GetEntryAsync<T>(string id);

We do not believe in this. The Contentful SDK is at its core an asynchronous library. Every call through the SDK is routed to the HttpClient and the SendAsync method. By exposing two methods to chose from, we are hiding this fact from the user and providing a less performant way to make the same call, which we believe makes no real sense. It's also hard for us to make speculations about consuming code, therefore by keeping everything asynchronous, we force ourselves not to depend on the threading environment and make sure our SDK is as performant and lean as possible.

If a consumer of the SDK still wishes to call the methods synchronously this is of course entirely up to him or her, but then at least you do so with your eyes open and fully aware that you are blocking asynchronous methods in a potentially wasteful way.

Final thoughts

This article has hopefully illustrated some of the important benefits of using asynchronous calls when calling external resources. It's not a simple matter to address, but the await and async keywords make it a whole lot more approachable than a couple of years ago. They are to be considered an essential part of every .NET developer's toolbox. Yes, async does tend to spread throughout your code once introduced and yes, wrapping every return type in a Task<T> is not the prettiest thing in the world. However, the benefits far outweigh the costs in this case.

So go ahead, get the SDK, test it, and let us know if you have any feedback on how we could improve on it: open an issue or submit a pull request, we're happy to help! If you want to know more about async/await, Contentful's .NET SDK, or talk about .NET in general, just hit me up on Twitter, I'll be happy to share the knowledge. And by the way, this is just the start for Contentful and .Net, we're planning on releasing more content in the upcoming weeks (did anyone say webinars?), so be sure to follow our official Twitter account @contentful, and you’ll be the first to know.

Blog posts in your inbox

Subscribe to receive most important updates. We send emails once a month.