Skip to content

Commit 37e1e3d

Browse files
Copilotdrewnoakes
andcommitted
Add CompletedTaskAttribute to TplExtensions and remove special-case code
Co-authored-by: drewnoakes <350947+drewnoakes@users.noreply.github.com>
1 parent 1b80964 commit 37e1e3d

3 files changed

Lines changed: 51 additions & 10 deletions

File tree

src/Microsoft.VisualStudio.Threading.Analyzers.CSharp/VSTHRD003UseJtfRunAsyncAnalyzer.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -148,16 +148,7 @@ private static bool IsSymbolAlwaysOkToAwait(ISymbol? symbol, Compilation compila
148148
}
149149
}
150150

151-
if (symbol is IFieldSymbol field)
152-
{
153-
// Allow the TplExtensions.CompletedTask and related fields.
154-
if (field.ContainingType.Name == Types.TplExtensions.TypeName && field.BelongsToNamespace(Types.TplExtensions.Namespace) &&
155-
(field.Name == Types.TplExtensions.CompletedTask || field.Name == Types.TplExtensions.CanceledTask || field.Name == Types.TplExtensions.TrueTask || field.Name == Types.TplExtensions.FalseTask))
156-
{
157-
return true;
158-
}
159-
}
160-
else if (symbol is IPropertySymbol property)
151+
if (symbol is IPropertySymbol property)
161152
{
162153
// Explicitly allow Task.CompletedTask
163154
if (property.ContainingType.Name == Types.Task.TypeName && property.BelongsToNamespace(Types.Task.Namespace) &&
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
6+
namespace Microsoft.VisualStudio.Threading;
7+
8+
/// <summary>
9+
/// Indicates that a property, method, or field returns a task that is already completed.
10+
/// This suppresses VSTHRD003 warnings when awaiting the returned task.
11+
/// </summary>
12+
/// <remarks>
13+
/// <para>
14+
/// Apply this attribute to properties, methods, or fields that return cached, pre-completed tasks
15+
/// such as singleton instances with well-known immutable values.
16+
/// The VSTHRD003 analyzer will not report warnings when these members are awaited,
17+
/// as awaiting an already-completed task does not pose a risk of deadlock.
18+
/// </para>
19+
/// <para>
20+
/// This attribute can also be applied at the assembly level to mark members in external types
21+
/// that you don't control:
22+
/// <code>
23+
/// [assembly: CompletedTask(Member = "ExternalLibrary.ExternalClass.CompletedTaskProperty")]
24+
/// </code>
25+
/// </para>
26+
/// </remarks>
27+
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Assembly, Inherited = false, AllowMultiple = true)]
28+
public sealed class CompletedTaskAttribute : Attribute
29+
{
30+
/// <summary>
31+
/// Initializes a new instance of the <see cref="CompletedTaskAttribute"/> class.
32+
/// </summary>
33+
public CompletedTaskAttribute()
34+
{
35+
}
36+
37+
/// <summary>
38+
/// Gets or sets the fully qualified name of the member that returns a completed task.
39+
/// This is only used when the attribute is applied at the assembly level.
40+
/// </summary>
41+
/// <remarks>
42+
/// The format should be: "Namespace.TypeName.MemberName".
43+
/// For example: "ExternalLibrary.ExternalClass.CompletedTaskProperty".
44+
/// </remarks>
45+
public string? Member { get; set; }
46+
}

src/Microsoft.VisualStudio.Threading/TplExtensions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,26 @@ public static partial class TplExtensions
2020
/// A singleton completed task.
2121
/// </summary>
2222
[Obsolete("Use Task.CompletedTask instead.")]
23+
[CompletedTask]
2324
public static readonly Task CompletedTask = Task.FromResult(default(EmptyStruct));
2425

2526
/// <summary>
2627
/// A task that is already canceled.
2728
/// </summary>
2829
[Obsolete("Use Task.FromCanceled instead.")]
30+
[CompletedTask]
2931
public static readonly Task CanceledTask = Task.FromCanceled(new CancellationToken(canceled: true));
3032

3133
/// <summary>
3234
/// A completed task with a <see langword="true" /> result.
3335
/// </summary>
36+
[CompletedTask]
3437
public static readonly Task<bool> TrueTask = Task.FromResult(true);
3538

3639
/// <summary>
3740
/// A completed task with a <see langword="false" /> result.
3841
/// </summary>
42+
[CompletedTask]
3943
public static readonly Task<bool> FalseTask = Task.FromResult(false);
4044

4145
/// <summary>

0 commit comments

Comments
 (0)