Asynchronous programming plays a vital role in modern software development, allowing us to efficiently utilize system resources and create responsive user interfaces. In the realm of C#, two powerful tools that facilitate asynchronous operations are Tasks and ValueTasks.
In this blog post, we’ll have a quick overview of Tasks and ValueTasks in C#. Prepare to optimize your code and unlock the full potential of asynchronous programming!
𝐖𝐡𝐚𝐭 𝐢𝐬 𝐚 𝐓𝐚𝐬𝐤?
In short, a Task is a class that is used for asynchronous programming to execute a single operation. If the operation needs to return a result, the class Task<TResult> is used.
𝐖𝐡𝐚𝐭 𝐢𝐬 𝐚 𝐕𝐚𝐥𝐮𝐞𝐓𝐚𝐬𝐤?
A ValueTask is a struct wrapper for the Task class. It basically has two fields, a field to store the Task/Task<TResult> and a field to store the TResult.
Since Task is a class, when it’s instantiated, it is stored on the heap. That means that, eventually, the garbage collector will be called to free the memory of the instantiated Task class after use.
ValueTask is a struct and, therefore, is stored on the stack. The benefit of this is that, as soon as the ValueTask is used, it is removed from the stack and that memory is freed.
𝐖𝐡𝐞𝐧 𝐬𝐡𝐨𝐮𝐥𝐝 𝐕𝐚𝐥𝐮𝐞𝐓𝐚𝐬𝐤 𝐛𝐞 𝐮𝐬𝐞𝐝 𝐢𝐧𝐬𝐭𝐞𝐚𝐝 𝐨𝐟 𝐓𝐚𝐬𝐤?
ValueTask should be used only when you expect an asynchronous method to run synchronously most of the time.
In the image provided as part of this post, you can see the code and the benchmark results for the same operation performed by the Task and ValueTask. We can see that, in this case, less memory is used by using ValueTask.
There are a few guidelines to follow when using ValueTask:
- ValueTask is a one-and-done deal. Don’t await the same ValueTask more than once as it may result in undefined behavior.
- ValueTask cannot be used with WhenAny(), WhenAll() methods. For that to work, we have to convert the ValueTask into a Task using AsTask().
- The same instance should not be awaited concurrently.
𝐓𝐥𝐝𝐫; ValueTask is good when used for asynchronous methods that will run synchronously most of the time since it can save memory. But it shouldn’t be awaited multiple times, it cannot be used with WhenAll(), WhenAny() methods and the same instance should not be awaited concurrently.
Have we missed anything?
Please, let us know in the comments below.