- 1. Available versions and reference materials
- 2.
ValueTask<TResult>
and Task - 3. How does the compiler compile
- 4. What are the advantages of ValueTask
- 5. ValueTask creates an asynchronous task
- 6. IValueTaskSource and custom packaging ValueTask
- 7. write an IValueTaskSource instance
- 8. use ManualResetValueTaskSourceCore
Recently, the NCC group is discussing ValueTask
/ValueTask<TResult>
. Dashuai (the main developer of Natasha) has recently been obsessed with algorithms and high-performance computing. He pays so much attention to this thing, which shows that there is something wrong with him. Learn it, lest there is no topic 🤣.
ValueTask
/ValueTask<TResult>
actually appeared earlier, and I haven't been in-depth before, so take this opportunity to learn.
When the article says ValueTask, in order to reduce the number of words, it generally includes its generic version ValueTask<TResult>
; when it mentions Task, it also includes its generic version;
1. Available versions and reference materials
According to the reference materials on the Microsoft website, the following versions of .NET programs (sets) can use ValueTask/ValueTask<TResult>
.
Version category | Version requirements |
---|---|
.NET | 5.0 |
.NET Core | 2.1, 3.0, 3.1 |
.NET Standard | 2.1 |
The following is the link address of the reference materials when the author read:
[1] [https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.valuetask?view=net-5.0]
[2] [https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.valuetask-1?view=net-5.0]
[3] [https://www.infoworld.com/article/3565433/how-to-use-valuetask-in-csharp.html]
[4] https://tooslowexception.com/implementing-custom-ivaluetasksource-async-without-allocations/
[5] [https://blog.marcgravell.com/2019/08/prefer-valuetask-to-task-always-and.html]
[6] https://qiita.com/skitoy4321/items/31a97e03665bd7bcc8ca
[7] https://neuecc.medium.com/valuetasksupplement-an-extensions-to-valuetask-4c247bc613ea
2. ValueTask<TResult>
and Task
ValueTask<TResult>
exists under the System.Threading.Tasks
namespace, and the definition of ValueTask<TResult>
is as follows:
public struct ValueTask<TResult>: IEquatable<ValueTask<TResult>>
The definition of Task is as follows:
public class Task: IAsyncResult, IDisposable
Judging from its inherited interfaces and official documents, the complexity of ValueTask<TResult>
should not be high.
According to the surface understanding of the document, this type should be a simplified version of Task. Task is a reference type, so the Task object is returned from an asynchronous method or every time an asynchronous method is called, the object will be allocated in the managed heap.
Based on the comparison, we should know:
- Task is a reference type and will allocate memory in the managed heap; ValueTask is a value type;
At present, there is only this point to remember. Let's continue to compare the similarities and differences between the two.
Here we try to compare Task with this type and see how the code is.
public static async ValueTask<int> GetValueTaskAsync()
{
await Task.CompletedTask; // Don't get me wrong here, this is just to find a random place to await
return 666;
}
public static async Task<int> GetTaskAsync()
{
await Task.CompletedTask;
return 666;
}</code></pre>
From the code point of view, the two methods used in simple code are the same (the CURD is basically the case).
3. How does the compiler compile
When Task is compiled, the state machine is generated by the compiler, and a class that inherits IAsyncStateMachine
is generated for each method, and there is a lot of code packaging.
According to my test, ValueTask also generates similar code.
As shown:

Visit https://sharplab.io/#gist:ddf2a5e535a34883733196c7bf4c55b2 to read the above code (Task) online.
Visit https://sharplab.io/#gist:7129478fc630a87c08ced38e7fd14cc0 to read the ValueTask sample code online.
You can visit these URLs separately to compare the differences.
The author has highlighted the differences, and readers can take a closer look:
Task:
[AsyncStateMachine(typeof(<GetTaskAsync>d__0))]
[DebuggerStepThrough]
public static Task<int> GetTaskAsync()
{
<GetTaskAsync>d__0 stateMachine = new <GetTaskAsync>d__0();
stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
stateMachine.<>1__state = -1;
AsyncTaskMethodBuilder<int> <>t__builder = stateMachine.<>t__builder;
<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
ValueTask:
[AsyncStateMachine(typeof(<GetValueTaskAsync>d__0))]
[DebuggerStepThrough]
public static ValueTask<int> GetValueTaskAsync()
{
<GetValueTaskAsync>d__0 stateMachine = new <GetValueTaskAsync>d__0();
stateMachine.<>t__builder = AsyncValueTaskMethodBuilder<int>.Create();
stateMachine.<>1__state = -1;
AsyncValueTaskMethodBuilder<int> <>t__builder = stateMachine.<>t__builder;
<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
I don't see the difference. . .
But here is the second point:
- If the processing speed of this method is very fast, or your code is immediately available after execution, etc., using asynchronous will not be faster than synchronous, but may cons
4. What are the advantages of ValueTask
From the previous content, we can see that ValueTask is the same as the state machine code generated after the Task is compiled. The real difference is that ValueTask is a value type, and Task is a reference type.
From a functional point of view, ValueTask is a simple asynchronous representation, and Task has many powerful methods and various operations.
ValueTask improves performance because it does not need to allocate memory on the heap. This is where ValueTask has an advantage over Task.
To avoid memory allocation overhead, we can use ValueTask to wrap the results that need to be returned.
public static ValueTask<int> GetValueTask()
{
return new ValueTask<int>(666);
}
public static async ValueTask<int> StartAsync()
{
return await GetValueTask();
}</code></pre>
But at present, we have not conducted any performance testing, which is not enough to illustrate the advantages of ValueTask in improving performance. The author will continue to explain some basic knowledge. When the time is ripe, we will conduct some tests and release sample codes.
5. ValueTask creates an asynchronous task
Let's take a look at the constructor definitions of ValueTask
and ValueTask<TResult>
.
// ValueTask
public ValueTask(Task task);
public ValueTask(IValueTaskSource source, short token);
// ValueTask<TResult>
public ValueTask(Task<TResult> task);
public ValueTask(TResult result);
public ValueTask(IValueTaskSource<TResult> source, short token);
If you create a task through Task, you can use new Task()
, Task.Run()
, etc. to create a task, and then you can use the async/await
keyword to define an asynchronous method and start an asynchronous task. So what if you use ValueTask?
In the fourth section, we already have an example, using the ValueTask(TResult result)
constructor, you can use new ValueTask
yourself, and then you can use the await
keyword.
In addition, there are multiple constructors of ValueTask, we can continue to dig.
Convert to ValueTask via Task:
public static async ValueTask<int> StartAsync()
{
Task<int> task = Task.Run<int>(() => 666);
return await new ValueTask<int>(task);
}
There is one method of IValueTaskSource
parameter type as the constructor function, we will put it to the sixth section.
The ValueTask instance can only wait once! This must be remembered!
6. IValueTaskSource and custom packaging ValueTask
About IValueTaskSource
IValueTaskSource is in the System.Threading.Tasks.Sources
namespace and is defined as follows:
public interface IValueTaskSource
{
void GetResult(short token);
ValueTaskSourceStatus GetStatus(short token);
void OnCompleted(
Action<object?> continuation,
object? state,
short token,
ValueTaskSourceOnCompletedFlags flags);
}</code></pre>
Method name
Function
GetResult(Int16)
Get the result of IValueTaskSource, only call once when the asynchronous state machine needs to get the operation result
GetStatus(Int16)
Get the status of the current operation, Called by the asynchronous state machine to check the operation status
OnCompleted(Action, Object, Int16, ValueTaskSourceOnCompletedFlags)
For this IValueTaskSource plans to continue the operation, developer calls it
In this namespace, there are some types related to ValueTask, please refer to [Microsoft documentation](https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.sources.ivaluetasksource? view=net-5.0).
Among the above three methods, OnCompleted
is used to continue the task. Readers familiar with Task should know this method, so I won't repeat it here.
We have an example earlier:
public static ValueTask<int> GetValueTask()
{
return new ValueTask<int>(666);
}
public static async ValueTask<int> StartAsync()
{
return await GetValueTask();
}</code></pre>
Simplified code after conversion by the compiler:
public static int _StartAsync()
{
var awaiter = GetValueTask().GetAwaiter();
if (!awaiter.IsCompleted)
{
// some inexplicable operation code
}
return awaiter.GetResult();
}</code></pre>
Based on this code, we found that ValueTask can be state-aware, so how to express that the task has been completed? What's the realization principle inside?
What is IValueTaskSource
IValueTaskSource is an abstraction through which we can separate the logical behavior of task/operation from the result itself (state machine).
Simplified example:
IValueTaskSource<int> someSource = // ...
short token = // ... token
var vt = new ValueTask<int>(someSource, token); // Create task
int value = await vt; // wait for the task to complete
But from this code, we can't see how to implement IValueTaskSource, and how IValueTaskSource is used inside ValueTask. Before delving into its principle, the author consulted from other blogs, documents and other places, in order to reduce the performance overhead of Task (introduced by C# 5.0), C# 7.0 appeared ValueTask. The emergence of ValueTask is to wrap the returned result and avoid the use of heap allocation.
Therefore, you need to use Task to convert to ValueTask:
public ValueTask(Task task); // ValueTask constructor
ValueTask just wraps the return result of Task.
Later, for higher performance, IValueTaskCource was introduced, and ValueTask added a constructor.
able to passImplement IValueTaskSource:
public ValueTask(IValueTaskSource source, short token); // ValueTask constructor
In this way, the performance overhead of the conversion between ValueTask and Task can be further eliminated. ValueTask has the ability to "manage" state and no longer depends on Task.
Let's talk about ValueTask advantages
In the coreclr draft on August 22, 2019, there is a topic "Make "async ValueTask/ValueTask" methods ammortized allocation-free", which discusses the performance impact of ValueTask and subsequent transformation plans in depth.
Issue address: https://github.com/dotnet/coreclr/pull/26310
There are various performance index comparisons, and I highly recommend readers who are interested in in-depth study to take a look at this Issue.
Don't implement IValueTaskSource all by yourself
Most people can't complete this interface. I personally don't understand it many times. After searching for a long time, I haven't found a suitable code example. According to the official documentation, I found ManualResetValueTaskSourceCore
, this type implements the IValueTaskSource
interface and is encapsulated, so we can use ManualResetValueTaskSourceCore
to wrap our own code to implement IValueTaskSource more easily.
Regarding ManualResetValueTaskSourceCore
, the usage method and code examples will be given later in the article.
ValueTaskSourceOnCompletedFlags
ValueTaskSourceOnCompletedFlags is an enumeration used to represent the behavior of continuation. The enumeration description is as follows:
Enumeration
Value
Description
FlowExecutionContext
2
OnCompleted
should capture the current ExecutionContext and use it to run the continuation.
None
0
There are any requirements in the call method of continuation.
UseSchedulingContext
1
OnCompleted
should capture the current scheduling context (SynchronizationContext) and use it when adding the continuation to the execution queue. If this flag is not set, the implementation can choose to perform a continuation at any position.
ValueTaskSourceStatus
The ValueTaskSourceStatus enumeration is used to indicate the status of IValueTaskSource or IValueTaskSource. The enumeration description is as follows:
Enumeration
Value
Description
Canceled
3
The operation was completed due to canceled operation.
Faulted
2
The operation has compl
Failed
2
The operation has completed with errors.
Pending
0
The operation has not yet completed.
Succeeded
1
The operation has completed successfully.
7. write an IValueTaskSource instance
Complete code: https://github.com/whuanle/RedisClientLearn/issues/1
If we want to design a Redis client and implement it asynchronously, if you have Socket development experience, you will understand that Socket does not send and receive one at a time. There is also no direct asynchronous interface in Socket in C#.
So here we have to implement an asynchronous Redis client.
Use IValueTaskSource to write a state machine:
// One can operate synchronous tasks and different threads synchronously, and construct asynchronous methods through the state machine
public class MyValueTaskSource<TRusult>: IValueTaskSource<TRusult>
{
// Store the returned result
private TRusult _result;
private ValueTaskSourceStatus status = ValueTaskSourceStatus.Pending;
// This task is abnormal
private Exception exception;
#region Implement the interface, tell the caller whether the task has been completed, whether there is a result, whether there is an exception, etc.
// Get results
public TRusult GetResult(short token)
{
// If there is an exception in this task, then when the result is obtained, re-pop
if (status == ValueTaskSourceStatus.Faulted)
throw exception;
// If the task is cancelled, an exception will also pop up
else if (status == ValueTaskSourceStatus.Canceled)
throw new TaskCanceledException("This task has been cancelled");
return _result;
}
// Get status, in this example, token is not used token
public ValueTaskSourceStatus GetStatus(short token)
{
return status;
}
// achieve continuation
public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)
{
// No need to continue, do not implement this interface
}
#endregion
#region implements a state machine, which can control whether this task has been completed and whether there is an exception
// and complete the task and give the result
public void SetResult(TRusult result)
{
status = ValueTaskSourceStatus.Succeeded; // This task has been completed
_result = result;
}
// cancel task
public void Cancel()
{
status = ValueTaskSourceStatus.Canceled;
}
// The task to be performed is abnormal
public void SetException(Exception exception)
{
this.exception = exception;
status = ValueTaskSourceStatus.Faulted;
}
#endregion
}</code></pre>
Fake Socket:
public class fake Socket
{
private bool IsHaveSend = false;
// Simulate Socket to send data to the server
public void Send(byte[] data)
{
new Thread(() =>
{
Thread.Sleep(100);
IsHaveSend = true;
}).Start();
}
// Synchronously block waiting for server response
public byte[] Receive()
{
// Simulate data transmitted over the network
byte[] data = new byte[100];
while (!IsHaveSend)
{
// When the server does not send data to the client, it has been waiting empty
}
// It takes time to simulate network receiving data
Thread.Sleep(new Random().Next(0, 100));
new Random().NextBytes(data);
IsHaveSend = false;
return data;
}
}</code></pre>
Implement Redis client and implement
// Redis client
public class RedisClient
{
// queue
private readonly Queue<MyValueTaskSource<string>> queue = new Queue<MyValueTaskSource<string>>();
private readonly fake Socket _socket = new fake Socket(); // a socket client
public RedisClient(string connectStr)
{
new Thread(() =>
{
while (true)
{
byte[] data = _socket.Receive();
// Take a state machine from the queue
if (queue.TryDequeue(out MyValueTaskSource<string> source))
{
// Set the result of this state machine
source.SetResult(Encoding.UTF8.GetString(data));
}
}
}).Start();
}
private void SendCommand(string command)
{
Console.WriteLine("The client sent a command:" + command);
_socket.Send(Encoding.UTF8.GetBytes(command));
}
public async ValueTask<string> GetStringAsync(string key)
{
// Custom state machine
MyValueTaskSource<string> source = new MyValueTaskSource<string>();
// Create an asynchronous task
ValueTask<string> task = new ValueTask<string>(source, 0);
// Join the queue
queue.Enqueue(source);
// Send the command to get the value
SendCommand($"GET {key}");
// Use await directly, only check the removal status! The first layer must complete the task before the inspection, and then it will fall into infinite waiting after await!
// return await task;
// To truly realize this kind of asynchrony, you must use SynchronizationContext and other complex structural logic!
// To avoid too much code, we can use the following infinite while method!
var awaiter = task.GetAwaiter();
while (!awaiter.IsCompleted) {}
// return result
return await task;
}
}</code></pre>
This is probably the idea. But in the end it is impossible to await directly like Task! ValueTask can only await once, and await can only be the final result check!
If we use TaskCompletionSource
to write the Task state machine, we can directly await.
If you want to truly implement ValueTask that can be awaited, when writing IValueTasksource
, you must implement SynchronizationContext
, TaskScheduler
, etc.
The implementation of these codes is more complicated, what should I do? Microsoft officially gave a ManualResetValueTaskSourceCore<TResult>
, with it, we can save a lot of complicated code!
ValueTask cannot be cancelled!
8. use ManualResetValueTaskSourceCore
Next, we use ManualResetValueTaskSourceCore
to transform the previous code, so that we can intuitively feel what this type is for!
Transform MyValueTaskSource
as follows:
// One can operate synchronous tasks and different threads synchronously, and construct asynchronous methods through the state machine
public class MyValueTaskSource<TRusult>: IValueTaskSource<TRusult>
{
private ManualResetValueTaskSourceCore<TRusult> _source = new ManualResetValueTaskSourceCore<TRusult>();
#region Implement the interface, tell the caller whether the task has been completed, whether there is a result, whether there is an exception, etc.
// Get results
public TRusult GetResult(short token)
{
return _source.GetResult(token);
}
// Get status, in this example, token is not used token
public ValueTaskSourceStatus GetStatus(short token)
{
return _source.GetStatus(token);;
}
// achieve continuation
public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)
{
_source.OnCompleted(continuation, state, token, flags);
}
#endregion
#region implements a state machine, which can control whether this task has been completed and whether there is an exception
// and complete the task and give the result
public void SetResult(TRusult result)
{
_source.SetResult(result);
}
// The task to be performed is abnormal
public void SetException(Exception exception)
{
_source.SetException(exception);
}
#endregion
}</code></pre>
After that, we can use await directly in GetStringAsync
!
public async ValueTask<string> GetStringAsync(string key)
{
// Custom state machine
MyValueTaskSource<string> source = new MyValueTaskSource<string>();
// Create an asynchronous task
ValueTask<string> task = new ValueTask<string>(source, 0);
// Join the queue
queue.Enqueue(source);
// Send the command to get the value
SendCommand($"GET {key}");
return await task;
}</code></pre>
So far, ValueTask, IValueTaskSource, ManualResetValueTaskSourceCore have been implemented which greatly simplifies the code and enhances asynchronous behavior.
eCore, have you figured it out!
Someone has implemented a lot of extensions to ValueTask, so that ValueTask has the same multi-task concurrency capability as Task, such as WhenAll, WhenAny, Factory, etc., expand the library address: https://github.com/Cysharp/ValueTaskSupplement
For time reasons, the author of this article will not give a comparison of GC and performance in concurrency and other situations. After you learn to use it, you can test it yourself.
文章评论