-
Notifications
You must be signed in to change notification settings - Fork 147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
VSTHRD002: Don't warn when getting Result on completed task #301
Comments
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 |
@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 EDIT: Added BenchmarkDotNet=v0.10.14, OS=Windows 7 SP1 (6.1.7601.0)
Intel Core i7-4770K CPU 3.50GHz (Haswell), 1 CPU, 4 logical and 4 physical cores
Frequency=3417031 Hz, Resolution=292.6517 ns, Timer=TSC
[Host] : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2650.0
DefaultJob : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2650.0
BenchmarkDotNetusing BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Threading.Tasks;
namespace VSThreading301
{
[BenchmarkDotNet.Attributes.DisassemblyDiagnoser(printAsm: false, printIL: true)]
public class BenchCompletedTask
{
private volatile Task<int> _completedTask = Task.FromResult(1);
const int Operations = 100000;
[Benchmark(OperationsPerInvoke = Operations)]
public async Task<int> ConfigureAwait_GetAwaiter_Async()
{
await _completedTask.ConfigureAwait(false);
int x = 0;
for (int i = 0; i < Operations; i++)
{
if (!_completedTask.IsCompleted)
await _completedTask.ConfigureAwait(false);
x += _completedTask.ConfigureAwait(false).GetAwaiter().GetResult();
}
return x;
}
[Benchmark(OperationsPerInvoke = Operations)]
public async Task<int> Await_ConfigureAwait_Async()
{
await _completedTask.ConfigureAwait(false);
int x = 0;
for (int i = 0; i < Operations; i++)
{
x += await _completedTask.ConfigureAwait(false);
}
return x;
}
[Benchmark(OperationsPerInvoke = Operations)]
public async Task<int> Await_Async()
{
await _completedTask.ConfigureAwait(false);
int x = 0;
for (int i = 0; i < Operations; i++)
{
x += await _completedTask;
}
return x;
}
[Benchmark(OperationsPerInvoke = Operations, Baseline = true)]
public async Task<int> Result_Async()
{
await _completedTask.ConfigureAwait(false);
int x = 0;
for (int i = 0; i < Operations; i++)
{
if (!_completedTask.IsCompleted)
await _completedTask.ConfigureAwait(false);
x += _completedTask.Result;
}
return x;
}
[Benchmark(OperationsPerInvoke = Operations)]
public async Task<int> GetAwaiter_Async()
{
await _completedTask.ConfigureAwait(false);
int x = 0;
for (int i = 0; i < Operations; i++)
{
if (!_completedTask.IsCompleted)
await _completedTask.ConfigureAwait(false);
x += _completedTask.GetAwaiter().GetResult();
}
return x;
}
}
public class Program
{
public static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<BenchCompletedTask>();
}
}
} |
Have you filed a bug over at dotnet/corefx for the slowness from Also, if you own the async method you're calling, given it usually is completed before returning have you considered using |
Good suggestion, I'll file it. Yes, I use cached tasks. And Cheers, |
Filed as https://github.com/dotnet/corefx/issues/30655, with additional tests and detail. |
💭 A |
I like it. In fact I already have such an extension method (I think I called it |
Diagnostic message: Avoid awaiting or returning a Task representing work that was not started within your context as that can lead to deadlocks. Related to microsoft/vs-threading#301, microsoft/vs-threading#335
We have another use case, apart from performance optimization: I use |
Bug description
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
thereforeis 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 @AArnott points out: guarding with
IsCompleted
will wrap completed exceptionsin an
AggregateException
. To avoid this, instead guard withIsCompletedSuccessfully
(ValueTask
or .Net Core 2.x) or
task.Status == TaskStatus.RanToCompletion
(.Net Framework), sincea successfully completed task won't have any exceptions.
Repro steps
Expected behavior
VSTHR002 does not trigger when the task has been checked as completed via:
IsCompleted
IsCanceled
IsFaulted
IsCompletedSuccessfully
(ValueTask
or .Net Core 2.x)Actual behavior
VSTHR002 warns even though the task has been checked for completion or have been awaited.
The text was updated successfully, but these errors were encountered: