You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
VSTHRD002: Don't warn when getting Result on completed task #301
VSTHR002 warns even though the task has been checked for completion or have been awaited.
It should not warn in these cases, since the task is provably completed, and Result therefore
is safe to use.
Not warning is important since the below is a common performance optimization - retrieving Result on a completed task is roughly 2x faster than awaiting the completed task.
Edited: As Andrew Arnott (@AArnott) points out: guarding with IsCompleted will wrap completed exceptions
in an AggregateException. To avoid this, instead guard with IsCompletedSuccessfully (ValueTask
or .Net Core 2.x) or task.Status == TaskStatus.RanToCompletion (.Net Framework), since
a successfully completed task won't have any exceptions.
Yes, I agree we should be smarter for this analyzer and not report where it's clear from code inspection of the method that the task has already completed. We've done some work in this area already (e.g. #219). What you ask for generally requires execution flow analysis, which the Roslyn API does not yet offer but I hear is coming. Our plan has been to address it then. However some common cases like what you pointed out could probably be hard-coded to be recognized and suppress the warning.
Not warning is important since the below is a common performance optimization - retrieving
Result on a completed task is roughly 2x faster than awaiting the completed task.
I think you should double-check your perf test here, because the C# code you wrote is virtually equivalent to the code that the C# compiler itself emits for an await. It literally checks IsCompleted first and accesses the Result synchronously when it is already completed and only schedules the continuation when it is not complete.
Also note that your "optimization" does change behavior in the failure case, leading to non-deterministic results. If task.IsCompleted is true when you check it, you'll call task.Result and throw an AggregateException. But when task.IsCompleted is false when you check it, then when you await you'll end up throwing the inner exception of the AggregateException (most likely a different exception type). Your race condition could lead to your exception handlers not firing consistently.
While you could solve the race condition by changing your task.Result call to task.GetAwaiter().GetResult() (which only differs in which exception is thrown), I suggest you use await consistently and trust the compiler to do that if check for you.
On performance, the differences certainly only matter with high volume and when a completed task is much (in my case +10,000 times) more common than an incomplete task. In my tests below, without ConfigureAwait() the difference is a negligible 15%. In my library though I use ConfigureAwait(false) everywhere, which makes it over 400% slower, and the ability to avoid that is (for me) hard to ignore. Yes one can add additional APIs to partly mitigate it, but multiple APIs for the same thing has its own drawbacks. If I'm missing something obvious though, I would be all ears :-)
EDIT: Added ConfigureAwait_GetAwaiter_Async(), showing that it's the ConfigureAwait(false) call that gives the +400% slowdown (not the await.)
Have you filed a bug over at dotnet/corefx for the slowness from .ConfigureAwait(false)? 400% slowdown seems incredibly high for a completed task.
Also, if you own the async method you're calling, given it usually is completed before returning have you considered using ValueTask<T> instead of Task<T>? That will avoid an allocation (although perhaps you're already caching a Task instance yourself) which can help throughput.
I like it. In fact I already have such an extension method (I think I called it GetResultAssumeCompleted()) for my own code clarify and enforcement. Adding such a method to vs-threading may be a good idea. But we might make the analyzer allow any method with that name regardless of where it comes from so that it can work on downlevel versions of VS.
We have another use case, apart from performance optimization: I use Task.IsCompletedSuccessfully to conditionally use Task.Result when I could already already use the result in case the task is finished, but I do not want to actively wait for the task to finish, as there are other things keeping the current task busy.
Activity
AArnott commentedon Jun 24, 2018
Yes, I agree we should be smarter for this analyzer and not report where it's clear from code inspection of the method that the task has already completed. We've done some work in this area already (e.g. #219). What you ask for generally requires execution flow analysis, which the Roslyn API does not yet offer but I hear is coming. Our plan has been to address it then. However some common cases like what you pointed out could probably be hard-coded to be recognized and suppress the warning.
I think you should double-check your perf test here, because the C# code you wrote is virtually equivalent to the code that the C# compiler itself emits for an await. It literally checks
IsCompletedfirst and accesses theResultsynchronously when it is already completed and only schedules the continuation when it is not complete.Also note that your "optimization" does change behavior in the failure case, leading to non-deterministic results. If
task.IsCompletedis true when you check it, you'll calltask.Resultand throw anAggregateException. But whentask.IsCompletedis false when you check it, then when you await you'll end up throwing the inner exception of theAggregateException(most likely a different exception type). Your race condition could lead to your exception handlers not firing consistently.While you could solve the race condition by changing your
task.Resultcall totask.GetAwaiter().GetResult()(which only differs in which exception is thrown), I suggest you use await consistently and trust the compiler to do that if check for you.KristianWedberg commentedon Jun 25, 2018
Andrew Arnott (@AArnott) All good points, my repo above was minimalistic.
On performance, the differences certainly only matter with high volume and when a completed task is much (in my case +10,000 times) more common than an incomplete task. In my tests below, without
ConfigureAwait()the difference is a negligible 15%. In my library though I useConfigureAwait(false)everywhere, which makes it over 400% slower, and the ability to avoid that is (for me) hard to ignore. Yes one can add additional APIs to partly mitigate it, but multiple APIs for the same thing has its own drawbacks. If I'm missing something obvious though, I would be all ears :-)EDIT: Added
ConfigureAwait_GetAwaiter_Async(), showing that it's theConfigureAwait(false)call that gives the +400% slowdown (not theawait.)BenchmarkDotNet
AArnott commentedon Jun 25, 2018
Have you filed a bug over at dotnet/corefx for the slowness from
.ConfigureAwait(false)? 400% slowdown seems incredibly high for a completed task.Also, if you own the async method you're calling, given it usually is completed before returning have you considered using
ValueTask<T>instead ofTask<T>? That will avoid an allocation (although perhaps you're already caching a Task instance yourself) which can help throughput.KristianWedberg commentedon Jun 25, 2018
Good suggestion, I'll file it.
Yes, I use cached tasks. And
ValueTask<T>has certainly been a life saver in some other places!Cheers,
Kristian
KristianWedberg commentedon Jun 25, 2018
Filed as https://github.com/dotnet/corefx/issues/30655, with additional tests and detail.
sharwell commentedon Jun 29, 2018
💭 A
CompletedResultextension method which throws if the task is not completed would help write clear (readable) code and also avoid this issue.AArnott commentedon Jun 29, 2018
I like it. In fact I already have such an extension method (I think I called it
GetResultAssumeCompleted()) for my own code clarify and enforcement. Adding such a method to vs-threading may be a good idea. But we might make the analyzer allow any method with that name regardless of where it comes from so that it can work on downlevel versions of VS.Add TplExtensions.CompletedResult
markusschaber commentedon Mar 31, 2023
We have another use case, apart from performance optimization: I use
Task.IsCompletedSuccessfullyto conditionally useTask.Resultwhen I could already already use the result in case the task is finished, but I do not want to actively wait for the task to finish, as there are other things keeping the current task busy.Bump powershell from 7.4.5 to 7.4.6 (#301)