Prevent creating two task waiting chains per AsyncLazy.GetValueAsync(CancellationToken) #1296
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
…CancellationToken) call.
Those appeared a lot during the solution open time, because some global AsyncLazy doesn't provide value until the blocking loading time ends. And cancellation token is often used to handle the case loading is aborted.
The reason why we didn't eliminate the duplication earlier is the JTF.JoinAsync and task.WithCancellation handles in-middle task continuation differently. The JTF one generally will capture the SynchorizationContext, while the task one would not. The exact performance charcter of the two choices is different. The JTF one prevent additional dependency to the thread pool in some cases, but may add unnecessary dependency to the UI thread in other cases, while the other one is on the reverse side. So eliminating the two chains could impact perf, because the accidental UI thread dependency is more likely to cause performance problems than the other one.
It is impossible to decide which behavior is better, because the best choice should be aligned to the ConfiguredAwaiter option to wait the task later. But provide this extra option would make API more complex. In this case, we try to make the behavior matching the exisiting GetValueAsync API. Capturing the context makes little sense for the task which is forgotten in the original code path.
This is an image of those extra tasks in a common solution loading session. We can see all of them waiting the same asyncLazy, and we have another set of almost same task chains to wait on the inner task. They are often chained to similar cancellation token, which bloats the cancellation token registration data structure.