Runtime: (Question) IValueTaskSource and ValueTask chaining

Created on 13 Jul 2020  路  7Comments  路  Source: dotnet/runtime

Hi team,

I've implemented some logic attempting to reduce allocations when working with methods returning ValueTask.
I've used the ManualResetValueTaskSourceCore inside a type implementing IValueTaskSource.

My API has a non async method returning ValueTask.

My question is if there is a simple way how to chain methods returning ValueTask while keeping allocations zero amortised?

Say i have public ValueTask MethodA and public ValueTask MethodB.

Both return ValueTasks from my custom IValueTaskSource.

Is there a simple way to do something like (this is prototype code):

var t1 = MethodA;
var t2 = MethodB;
await t1;
await t2;

, while achieving zero amortised allocs?

area-System.Threading.Tasks question

Most helpful comment

An async ValueTask that runs synchronously does not allocate anything. For the async case, you'll need some sort of state for this. You can certainly manage that yourself, or you can let DOTNET_SYSTEM_THREADING_POOLASYNCVALUETASKS do that for you.

Because ValueTask has some gotchas, the recommendation for success is to always await a ValueTask return directly rather than (as you have done in your example) store them in temp variables. This is the reason we don't have a built-in ValueTask.WhenAll.

All 7 comments

Tagging subscribers to this area: @tarekgh
Notify danmosemsft if you want to be subscribed.

CC @stephentoub

Is the idea that to achieve this natively we'd use .Net 5 with DOTNET_SYSTEM_THREADING_POOLASYNCVALUETASKS?

An async ValueTask that runs synchronously does not allocate anything. For the async case, you'll need some sort of state for this. You can certainly manage that yourself, or you can let DOTNET_SYSTEM_THREADING_POOLASYNCVALUETASKS do that for you.

Because ValueTask has some gotchas, the recommendation for success is to always await a ValueTask return directly rather than (as you have done in your example) store them in temp variables. This is the reason we don't have a built-in ValueTask.WhenAll.

Yes i understand the limitations of using ValueTask (double awaits etc). Tbh the interleaved execution in the above example is not even critical for the question. I was excited to be able to write allocation free async code, until it dawned on me that combining custom backed ValueTasks is still problematic (unless i'm missing something). I just want to clarify that it is not sufficient for both MethodA and MethodB to return IValueTaskSource backed ValueTasks, in order for the combination of them in some other method to be zero amortised allocation?

ValueTask MethodA

ValueTask MethodB

async ValueTask Combine
=> await MethodA+ await MethodB

I just want to clarify that it is not sufficient for both MethodA and MethodB to return IValueTaskSource backed ValueTasks, in order for the combination of them in some other method to be zero amortised allocation?

Correct. An async operation that consumes two other async operations is still its own async operation; if the goal is to have that be an IValueTaskSource implementation, you would need to craft an implementation of IValueTaskSource which provides the relevant implementation. That's what DOTNET_SYSTEM_THREADING_POOLASYNCVALUETASKS does.

@stephentoub Thank you. I want to give you some details so you know of another use case where this makes a difference.

I have an application that sends Kafka messages and wait for another kafka message before finishing the call.

The flow of the method is:
1.Await a task from a TaskCompletionSource. The task of the TCS might handle the continuations of many concurrent calls
2.Send a Kafka message
3.Wait for a Kafka message
4.Complete request

Step 3 (the waiting) used to be based on another TaskCompletionSource. I replaced it with a IValueTaskSource backed VT. It improved throughput by 40%. The last allocation is the StateMachineBox (because i await the first TCS). I had a suspicion that removing allocations would improve throughput, and moving to IValueTaskSource made a difference. Am curious now what will happen if i use DOTNET_SYSTEM_THREADING_POOLASYNCVALUETASKS.

Was this page helpful?
0 / 5 - 0 ratings