Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / .NET

Asynchronous Programming in .NET – Benefits and Tradeoffs of Using ValueTask

3.59/5 (11 votes)
11 Jun 2018CPOL6 min read 12.4K  
Benefits and Tradeoffs of Using ValueTask in .NET
In this article, we will take a look at ValueTask structure. As a value type, it comes with all the problems that value types in general have. So, we can reap the benefits of this structure, but we have to be cautious.

In the previous articles, we explored various aspects of asynchronous programming in .NET. We talked about the motivation behind this way of programming and about some general pitfalls and guidelines. Apart from that, we explored Task-Based Asynchronous Pattern. In that article, we saw how we can use Task and Task class for making our code asynchronous and reap the benefits of parallel execution of the code. However, using Task or Task class has one performance bottleneck that we haven’t mentioned in the previous articles.

In a nutshell, these classes cause the unnecessary allocation when the result is immediately available. This means, that even if a result is already available, a new object of Task or Task will always be created. Now, we mentioned that the async/await concept that we used in the previous articles has been around since 4.5 version of .NET. This feature has been enhanced since C# 7, i.e., .NET 4.7 version with ValueTask structure, that can be the return of the async functions.

ValueTask Structure

Image 1

ValueTask structure first appeared on corefxlab repository back in 2015. This repository is used for experimentation and exploring new ideas that may or may not make it into the main corefx repository. Corefx repository is the repository where all .NET Core fundamental libraries are. It was developed and suggested by the Stephen Taub for System.Threading.Tasks.Channels library. Back then, Stephen provided a short explanation of it:

ValueTask is a discriminated union of a T and a Task, making it allocation-free for ReadAsync to synchronously return a T value it has available (in contrast to using Task.FromResult, which needs to allocate a Task instance). ValueTask is awaitable, so most consumption of instances will be indistinguishable from with a Task.

A lot of people saw benefits of using this structure and it was included in C#7 as a part of System.Threading.Tasks.Extensions NuGet package. So, before we dive into the ValueTask structure, let’s examine the problem it is used to solve. Since Task (Task) is a reference type, returning Task object from async method means allocating it on heap every time. And this is needed in many cases.

However, there are cases where the async method returns a cached result or completes synchronously. In these cases, this allocation is unnecessary and can become costly in performance critical parts of the code. Up to .NET 4.7 version, there was no way to avoid this because an async method had to return Task, Task<T>, or void (the last one is generally frowned upon). In this version of .NET, this was extended, meaning async methods could return any type as long as it has GetAwaiter method accessible. ValueTask is a concrete example of such type, that was added to this version too.

You can explore corefx repository and take a look at the complete implementation of ValueTask, but here is the part of the API that we are interested in:

C#
public ValueTask(TResult result);
public ValueTask(Task<TResult> task);
public static implicit operator ValueTask<TResult>(Task<TResult> task);
public static implicit operator ValueTask<TResult>(TResult result);
public Task<TResult> AsTask();
public bool IsCompletedSuccessfully { get; }
public TResult Result { get; }
public ValueTaskAwaiter GetAwaiter();
public ValueTaskAwaiter ConfigureAwait(bool continueOnCapturedContext);
// ...

Being a structure, ValueTask enables writing async methods that do not allocate memory when running synchronously. API consistency of async/await concept has not been compromised this way. Apart from that, this structure can be awaited by itself which makes it easy to use. For example, if we run this simple code:

C#
public void Example()
{
    int res = MultipyAsync(0, 0).Result;
    Console.WriteLine(res);
}

private async Task<int> MultipyAsync(int x, int y)
{
    if (x == 0 || y == 0)
    {
        return 0;
    }

    return await Task.Run(() => x * y);
}

In MultiplyAsync method, we are simulating the case where we want to avoid the use of the Task and just return a simple integer. That is done in the if statement of the method, where we are basically checking if either of the passed arguments is zero. The problem is that the above code will create a Task object even if the condition in the if statement is true. We solve this problem like this:

C#
public void Example()
{
    int res = MultiplyAsync(0, 0).Result;
    Console.WriteLine(res);
}

private async ValueTask<int> MultiplyAsync(int x, int y)
{
    if (x == 0 || y == 0)
    {
        return 0;
    }

    return await Task.Run(() => x * y);
}

Benefits and Tradeoffs

Image 2

As mentioned previously, there are two main benefits of using ValueTask:

  • Performance improvements
  • Increased implementation flexibility

So, what are the numbers behind the performance improvements? Observe this code:

C#
public async Task<int> TestTask(int delay)
{
    await Task.Delay(delay);
    return 1;
}

If we run this code, it will take 120ns with JIT to be executed. Now, if we replace Task with ValueTask like this:

C#
public async ValueTask<int> TestValueTask(int delay)
{
    await Task.Delay(delay);
    return 1;
}

we would get the execution time of 65ns with JIT. Now, it is true that because of the Task.Delay, we don’t have the synchronous execution, still, we see improvements in the execution time.

Another benefit that we mentioned is increased implementation flexibility. Now, what exactly does this mean? Well, implementations of an async interface that should be synchronous would be forced to use either Task.Run or Task.FromResult. This would, of course, result in performance issues that we discussed previously. When we use ValueTask, we have more possibility to choose between synchronous or asynchronous implementation. Keep in mind that this may indicate that if you have this case, your code might not be designed well.

For example, observe this interface:

C#
public interface IThing<T>
{
    ValueTask<T> DoTheThingAsync();
}

And let’s say that you want to call it from the code like this:

C#
IThing<T> thing = thingFactory.getThing();
var x = await thing.DoTheThingAsync();

Because we used ValueTask in the interface, implementation of that interface can be either synchronous or asynchronous. We can reap this benefit and basically skip adding some functions into IThing that would handle synchronous behaviors. Using this interface is much easier this way. Here is a synchronous implementation of the interface above:

C#
public class SynchronousThing<T> : IThing<T>
{
    public ValueTask<T> DoTheThingAsync()
    {
        var value = default(T);
        return new ValueTask<T>(value);
    }
}

And here is an asynchronous implementation of the same interface:

C#
public class AsynchronousThing<T> : IThing<T>
{
    public ValueTask<T> DoTheThingAsync()
    {
        var value = default(T);
        await Task.Delay(100);
        return new ValueTask<T>(value);
    }
}

However, there are some tradeoffs that we must take into consideration before using ValueTask. It is easy to think that using of ValueTask instead of Task should be done by default, which is of course not the case. For example, even though ValueTask helps us avoid unnecessary allocation in the cases when a result is available synchronously, it also contains two fields.

It is important to remember that this is a structure we use here, meaning we use value type and all its burdens. The Task on the other hand, is a reference type with just one field. When you use ValueTaskwe have more data to handle and take care of. If such method is awaited within an async method, the state machine for that async method will be larger as well, because storing the whole structure, in general, requires more space than storing single reference.

This is why guys from Microsoft are actually suggesting to use Task or Task as default return type form async methods. Only after performance analysis, one should consider using ValueTask instead.

Conclusion

ValueTask is a structure that was introduced in .NET 4.7 and that gives us quite a few possibilities when working with asynchronous methods within .NET. However, it doesn’t come without a price. It is quite good for performance critical methods that are executed synchronously. Using them, we avoid allocation of the unnecessary objects. Still, as a value type, it comes with all the problems that value types in general have. So, we can reap benefits of this structure, but we have to be cautious.

History

  • 11th June, 2018: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)