A newer version of this documentation is available.

View Latest

Asynchronous Programming Using the .NET SDK with Couchbase Server

Introduction

Asynchronous and reactive methods allow you to utilize system resources better. Instead of wasting a thread waiting for network or disk I/O, it can be fully utilized to perform other work instead.

If you are programming in .NET there are a wide variety of technologies that support asynchronous programming from the Asynchronous Programming Model (APM) and the Event-based Asynchronous Pattern (EAP) within the .NET Framework to OSS frameworks built for the asynchronous model like Akka.NET.

Your database access must meet the following requirements:

  • Rich functionality

  • Interoperable and not opinionated

  • Performant

  • Small dependency and runtime footprint

With the addition of the Task-based Asynchronous Pattern (TAP) released in .NET 4 the Task and Task<Result> "futures" Types were added along with language support for the async and await keywords. TAP offers an easy to use, consistent model for performing asynchronous programming and it is a natural fit to support TAP in the Couchbase .NET SDK.

After becoming more familiar with the concept, you will want to use more asynchronous and reactive approaches in your applications. Couchbase fully supports blocking operations as well, so you can still use a traditional blocking-based model if you want to.

The Async and Await Keywords

In modern .NET applications the async and await keywords are used to perform asynchronous programming. The async keyword is part of the signature of the method, lambda expression or anonymous that will be executing the non-blocking, asynchronous code. A method signature with the the async keyword is called an "async method".

public async Task<IDocumentResult<T>> GetDocumentAsync<T>(string id){
    //code goes here
}

Above is the signature of the GetDocumentAsync method of the CouchbaseBucket class in the SDK. The async keyword will enable the await keyword to within the method body. If the await keyword is not found in the method body, a compiler warning will be generated; if the body of the method contains an await keyword and the signature does not contain the async keyword, a compiler error will be generated. A method body can contain multiple await keywords. The "Async" suffix is simply a convention to make it easier to tell that a method is an asynchronous method.

When the GetDocumentAsync method is called, it will run synchronously until an await keyword is encountered. At that point the execution will be suspended until the Task or Task<Result> that was awaited on, returns. At this point the control will return back to the caller of the method. If the Task has already completed, execution will continue on synchronously (there is no point in offloading execution to the thread pool if the task has already completed).

Task and Task<Result>

In TAP, the Task and Task<Result> are equivalent to "futures"– a special type that will notify the caller when they have completed. A Task represents a void return type (a method whose return type is void) and a Task<Result> represents the return type of the generic result - in the GetDocumentAsync method above, its a class that implements IDocumentResult. Within your code you will await on the Task or Task<Result> returned by the asynchronous method you are calling:

DocumentResult<string> docResult = await GetDocumentAsync<string>("somekey");

Note that if the await keyword is omitted, then a Task&lt;IDocumentResult&lt;string>> would be returned:

Task<DocumentResult<string>> docTask = GetDocumentAsync<string>("somekey");

In this case the Task has not been awaited and it may or may have not been executed (remember its a "future"). In order to execute it the docResult would have to be awaited:

DocumentResult<string> docResult = await docTask;

In this case we have seperated out the call to GetDocumentAsync(..) and awaiting for the execution of the Task<DocumentResult> it returned to be completed. Note that by the time docTask has been awaited, the execution may actually have already been completed. In this case the await will simply return the result of the operation; the document stored under the id or key "somekey".

An In-depth Example

Continuing from the section above, we will make a call to retrieve a document with an Id of "somekey" and display the results. We will assume that the server is installed on localhost and that we have a "default" CouchbaseBucket set up.

static void Main(string[] args){
    Console.WriteLine("Before calling PrintDocumentAsync on thread {0}.",
       Thread.CurrentThread.ManagedThreadId);
    new AsyncExample().ExecuteAsync().Wait();
    Console.WriteLine("After calling PrintDocumentAsync on thread {0}.",
     Thread.CurrentThread.ManagedThreadId);
}

public override async Task ExecuteAsync(){
    //call it asynchronously with await
    await PrintDocumentAsync("somekey");
}

public async Task PrintDocumentAsync(string id){
    Console.WriteLine("Before awaiting GetDocumentAsync on thread {0}.",
        Thread.CurrentThread.ManagedThreadId);
    var doc = await _bucket.GetDocumentAsync<string>(id);
    Console.WriteLine("After awaiting GetDocumentAsync on thread {0}.",
        Thread.CurrentThread.ManagedThreadId);
    Console.WriteLine(doc.Content);
}

In this example (full source in AsyncExample.cs) we construct a simple .NET Console application that initializes a ClusterHelper and makes a single call to an async method we have defined called PrintDocumentAsync(..). To demonstrate that we are doing asynchronous programming and that the Task is being executed on a thread other than the main thread, we put some stdio output that shows the order of execution and then thread the code is running on:

Before calling PrintDocumentAsync on thread 8.
After calling PrintDocumentAsync on thread 8.
Before awaiting GetDocumentAsync on thread 10.
After awaiting GetDocumentAsync on thread 15.

In the output above we see that the order of execution went from the main thread (8) to the main thread before jumping onto thread ten (10) and finally completing on thread fifteen (15)! This make sense because this is completely non-blocking code until we hit Console.Read().

Calling TAP Methods Synchronously

The SDK has an equivalent non-async API with the only difference being that the method signatures are slightly different. For example, the synchronous version of GetDocumentAsync() looks like this:

public IDocumentResult<T> GetDocument<T>(string id){
    ...
}

However, in some situations you may want to call the asynchronous methods synchronously. To do this you would not use the async and await keywords (since you are not awaiting anything); instead you would use Task.Wait(..) and Task.Result:

using System;
static void Main(string[] args){
    Console.WriteLine("Before calling PrintDocumentAsync on thread {0}.",
      Thread.CurrentThread.ManagedThreadId);
    new SyncExample().ExecuteAsync().Wait();
    Console.WriteLine("After calling PrintDocumentAsync on thread {0}.",
        Thread.CurrentThread.ManagedThreadId);
}

public override Task ExecuteAsync(){
    //call it synchronously with no await
    PrintDocumentAsync("somekey").Wait();
    return Task.FromResult(0);
}

public Task PrintDocumentAsync(string id){
    Console.WriteLine("Before awaiting GetDocumentAsync on thread {0}.",
        Thread.CurrentThread.ManagedThreadId);
    var doc = _bucket.GetDocumentAsync<string>(id).Result;
    Console.WriteLine("After awaiting GetDocumentAsync on thread {0}.",
        Thread.CurrentThread.ManagedThreadId);
    Console.WriteLine(doc.Content);
    return Task.FromResult(0);
}

Note that by calling these asynchronous methods synchronously, you are adding additional overhead which is why in most cases you’ll want to just use the synchronous API instead. The entire source for this example is in SyncExample.cs.

Batch Execution of Tasks

A common use-case is to execute a series of operations in parallel in a batch-like fashion. To do this you’ll create a list of Task operations and then await on Task.WhenAll(..) to execute them:

static void Main(string[] args){
    new AsyncBatch().ExecuteAsync().Wait();
    Console.Read();
}

public override async Task ExecuteAsync(){
    var ids = new List<string> { "doc1", "doc2", "doc4" };
    await PrintAllDocumentsAsync(ids);
}

public async Task PrintAllDocumentsAsync(List<string> ids){
    var tasks = new List<Task<IDocumentResult<string>>>();
    ids.ForEach(x => tasks.Add(_bucket.GetDocumentAsync<string>(x)));

    var results = await Task.WhenAll(tasks);
    results.ToList().ForEach(doc => Console.WriteLine(doc.Status));
}

The Task.WhenAll(..) method execute the entire list of in parallel in a non-blocking, asynchronous fashion. Note that there is a similarly named method for executing the list in a blocking manner: Task.WaitAll(..). Depending upon your use-case you should understand the subtle differences between these two methods and use the one that fits your scenario. In most cases this will be Task.WhenAll(..). The source for this example can be found in the AsyncBatch.cs example.

Handling Synchronization Contexts

Every CLR hosting environment (ASP.NET, WinForms, etc) has its own specific SynchronizationContext for handling multi-threaded programs. Its important to note that in some cases (especially ASP.NET) using the default SynchronizationContext may cause deadlocks. The simplest way to handle these deadlocks is by using Task.ConfigureAwait(false). You can read more about this in Stephen Cleary’s blog on async and blocking.