From 7d17e8b8d40f5922af6f04a043f677c434b09e62 Mon Sep 17 00:00:00 2001 From: Lifeng Lu Date: Sat, 19 Apr 2025 12:29:47 -0700 Subject: [PATCH 1/2] For customized SynchronizationContext implementation, knowing whether a pending request has been completed could allow it to remove completed requests. Because JTF requests are potentially sent to multiple queues, including the JTF internal queue, many of those requests can be processed especially inside low priority or delayed queues. For low priority queue, completed tasks showing up for high memory usages. This new contract provides a way to make improvements. This is only for advanced scenarios, common JTF consumers would not access the request or have chance to use it incorrectly. --- .../IPendingExecutionRequestState.cs | 16 ++++++++++++++++ .../JoinableTaskFactory.cs | 7 ++++++- .../net472/PublicAPI.Unshipped.txt | 2 ++ .../net8.0-windows/PublicAPI.Unshipped.txt | 2 ++ .../net8.0/PublicAPI.Unshipped.txt | 2 ++ .../netstandard2.0/PublicAPI.Unshipped.txt | 2 ++ 6 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/Microsoft.VisualStudio.Threading/IPendingExecutionRequestState.cs diff --git a/src/Microsoft.VisualStudio.Threading/IPendingExecutionRequestState.cs b/src/Microsoft.VisualStudio.Threading/IPendingExecutionRequestState.cs new file mode 100644 index 000000000..7a745cb53 --- /dev/null +++ b/src/Microsoft.VisualStudio.Threading/IPendingExecutionRequestState.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.Threading +{ + /// + /// An optional interface implemented by pending request state posted to the underline synchronization context. It allows synchronization context to remove completed requests. + /// + public interface IPendingExecutionRequestState + { + /// + /// Gets a value indicating whether the current request has been completed, and can be skipped. + /// + bool IsCompleted { get; } + } +} diff --git a/src/Microsoft.VisualStudio.Threading/JoinableTaskFactory.cs b/src/Microsoft.VisualStudio.Threading/JoinableTaskFactory.cs index 018f4c4bc..e32d3dd72 100644 --- a/src/Microsoft.VisualStudio.Threading/JoinableTaskFactory.cs +++ b/src/Microsoft.VisualStudio.Threading/JoinableTaskFactory.cs @@ -1058,7 +1058,7 @@ public void Dispose() /// A delegate wrapper that ensures the delegate is only invoked at most once. /// [DebuggerDisplay("{DelegateLabel}")] - internal class SingleExecuteProtector + internal class SingleExecuteProtector : IPendingExecutionRequestState { /// /// Executes the delegate if it has not already executed. @@ -1112,6 +1112,11 @@ private SingleExecuteProtector(JoinableTask job) this.job = job; } + /// + /// Gets a value indicating whether the current request has been completed, and can be skipped. + /// + bool IPendingExecutionRequestState.IsCompleted => this.HasBeenExecuted; + /// /// Gets a value indicating whether this instance has already executed. /// diff --git a/src/Microsoft.VisualStudio.Threading/net472/PublicAPI.Unshipped.txt b/src/Microsoft.VisualStudio.Threading/net472/PublicAPI.Unshipped.txt index 0e00ec898..82ffde8aa 100644 --- a/src/Microsoft.VisualStudio.Threading/net472/PublicAPI.Unshipped.txt +++ b/src/Microsoft.VisualStudio.Threading/net472/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ Microsoft.VisualStudio.Threading.AsyncBarrier.SignalAndWait(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +Microsoft.VisualStudio.Threading.IPendingExecutionRequestState +Microsoft.VisualStudio.Threading.IPendingExecutionRequestState.IsCompleted.get -> bool static Microsoft.VisualStudio.Threading.JoinableTaskContext.CreateNoOpContext() -> Microsoft.VisualStudio.Threading.JoinableTaskContext! \ No newline at end of file diff --git a/src/Microsoft.VisualStudio.Threading/net8.0-windows/PublicAPI.Unshipped.txt b/src/Microsoft.VisualStudio.Threading/net8.0-windows/PublicAPI.Unshipped.txt index 0e00ec898..82ffde8aa 100644 --- a/src/Microsoft.VisualStudio.Threading/net8.0-windows/PublicAPI.Unshipped.txt +++ b/src/Microsoft.VisualStudio.Threading/net8.0-windows/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ Microsoft.VisualStudio.Threading.AsyncBarrier.SignalAndWait(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +Microsoft.VisualStudio.Threading.IPendingExecutionRequestState +Microsoft.VisualStudio.Threading.IPendingExecutionRequestState.IsCompleted.get -> bool static Microsoft.VisualStudio.Threading.JoinableTaskContext.CreateNoOpContext() -> Microsoft.VisualStudio.Threading.JoinableTaskContext! \ No newline at end of file diff --git a/src/Microsoft.VisualStudio.Threading/net8.0/PublicAPI.Unshipped.txt b/src/Microsoft.VisualStudio.Threading/net8.0/PublicAPI.Unshipped.txt index 0e00ec898..82ffde8aa 100644 --- a/src/Microsoft.VisualStudio.Threading/net8.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.VisualStudio.Threading/net8.0/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ Microsoft.VisualStudio.Threading.AsyncBarrier.SignalAndWait(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +Microsoft.VisualStudio.Threading.IPendingExecutionRequestState +Microsoft.VisualStudio.Threading.IPendingExecutionRequestState.IsCompleted.get -> bool static Microsoft.VisualStudio.Threading.JoinableTaskContext.CreateNoOpContext() -> Microsoft.VisualStudio.Threading.JoinableTaskContext! \ No newline at end of file diff --git a/src/Microsoft.VisualStudio.Threading/netstandard2.0/PublicAPI.Unshipped.txt b/src/Microsoft.VisualStudio.Threading/netstandard2.0/PublicAPI.Unshipped.txt index 0e00ec898..82ffde8aa 100644 --- a/src/Microsoft.VisualStudio.Threading/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.VisualStudio.Threading/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ Microsoft.VisualStudio.Threading.AsyncBarrier.SignalAndWait(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +Microsoft.VisualStudio.Threading.IPendingExecutionRequestState +Microsoft.VisualStudio.Threading.IPendingExecutionRequestState.IsCompleted.get -> bool static Microsoft.VisualStudio.Threading.JoinableTaskContext.CreateNoOpContext() -> Microsoft.VisualStudio.Threading.JoinableTaskContext! \ No newline at end of file From 37f9be019fd92a22b2c563c7ed5a242e656ca6aa Mon Sep 17 00:00:00 2001 From: Lifeng Lu Date: Tue, 22 Apr 2025 14:35:26 -0700 Subject: [PATCH 2/2] Mark new contract an experimental feature. --- .../IPendingExecutionRequestState.cs | 5 +++++ src/Microsoft.VisualStudio.Threading/JoinableTaskFactory.cs | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/Microsoft.VisualStudio.Threading/IPendingExecutionRequestState.cs b/src/Microsoft.VisualStudio.Threading/IPendingExecutionRequestState.cs index 7a745cb53..9d73c4e8c 100644 --- a/src/Microsoft.VisualStudio.Threading/IPendingExecutionRequestState.cs +++ b/src/Microsoft.VisualStudio.Threading/IPendingExecutionRequestState.cs @@ -1,11 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; + namespace Microsoft.VisualStudio.Threading { /// /// An optional interface implemented by pending request state posted to the underline synchronization context. It allows synchronization context to remove completed requests. /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Experimental("VSOnly")] public interface IPendingExecutionRequestState { /// diff --git a/src/Microsoft.VisualStudio.Threading/JoinableTaskFactory.cs b/src/Microsoft.VisualStudio.Threading/JoinableTaskFactory.cs index e32d3dd72..8395363b9 100644 --- a/src/Microsoft.VisualStudio.Threading/JoinableTaskFactory.cs +++ b/src/Microsoft.VisualStudio.Threading/JoinableTaskFactory.cs @@ -1058,7 +1058,9 @@ public void Dispose() /// A delegate wrapper that ensures the delegate is only invoked at most once. /// [DebuggerDisplay("{DelegateLabel}")] +#pragma warning disable VSOnly // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. internal class SingleExecuteProtector : IPendingExecutionRequestState +#pragma warning restore VSOnly // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. { /// /// Executes the delegate if it has not already executed.