From 42bb222ffaddedc1da2e985776cd88affa9f4c36 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 8 Apr 2025 16:38:07 -0500 Subject: [PATCH 1/4] Eliminate TPL to APM implementation in favor of MSDN guide. --- .../Data/SqlClient/SqlSequentialStream.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs index c97e51f577..2ac535a967 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs @@ -4,13 +4,14 @@ using System; using System.Diagnostics; +using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Common; namespace Microsoft.Data.SqlClient { - sealed internal class SqlSequentialStream : System.IO.Stream + internal sealed class SqlSequentialStream : Stream { private SqlDataReader _reader; // The SqlDataReader that we are reading data from private readonly int _columnIndex; // The index of out column in the table @@ -328,11 +329,24 @@ public override int EndRead(IAsyncResult asyncResult) return readTask.Result; } #else - public override IAsyncResult BeginRead(byte[] array, int offset, int count, AsyncCallback asyncCallback, object asyncState) => - TaskToApm.Begin(ReadAsync(array, offset, count, CancellationToken.None), asyncCallback, asyncState); + public override IAsyncResult BeginRead( + byte[] array, + int offset, + int count, + AsyncCallback asyncCallback, + object asyncState) + { + Task readTask = ReadAsync(array, offset, count, CancellationToken.None); + if (asyncCallback is not null) + { + readTask.ContinueWith(result => asyncCallback(result)); + } + + return readTask; + } public override int EndRead(IAsyncResult asyncResult) => - TaskToApm.End(asyncResult); + ((Task)asyncResult).Result; #endif } } From 28e8ad50fdc6591232defead5c88a5f52f44df94 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 8 Apr 2025 16:40:29 -0500 Subject: [PATCH 2/4] Remove TaskToApm --- .../System/Threading/Tasks/TaskToApm.cs | 189 ------------------ .../src/Microsoft.Data.SqlClient.csproj | 1 - 2 files changed, 190 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/netcore/src/Common/System/Threading/Tasks/TaskToApm.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Common/System/Threading/Tasks/TaskToApm.cs b/src/Microsoft.Data.SqlClient/netcore/src/Common/System/Threading/Tasks/TaskToApm.cs deleted file mode 100644 index add41f588e..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Common/System/Threading/Tasks/TaskToApm.cs +++ /dev/null @@ -1,189 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// Helper methods for using Tasks to implement the APM pattern. -// -// Example usage, wrapping a Task-returning FooAsync method with Begin/EndFoo methods: -// -// public IAsyncResult BeginFoo(..., AsyncCallback callback, object state) -// { -// Task t = FooAsync(...); -// return TaskToApm.Begin(t, callback, state); -// } -// public int EndFoo(IAsyncResult asyncResult) -// { -// return TaskToApm.End(asyncResult); -// } - -using System.Diagnostics; - -namespace System.Threading.Tasks -{ - /// - /// Provides support for efficiently using Tasks to implement the APM (Begin/End) pattern. - /// - internal static class TaskToApm - { - /// - /// Marshals the Task as an IAsyncResult, using the supplied callback and state - /// to implement the APM pattern. - /// - /// The Task to be marshaled. - /// The callback to be invoked upon completion. - /// The state to be stored in the IAsyncResult. - /// An IAsyncResult to represent the task's asynchronous operation. - public static IAsyncResult Begin(Task task, AsyncCallback callback, object state) - { - Debug.Assert(task != null); - - // If the task has already completed, then since the Task's CompletedSynchronously==false - // and we want it to be true, we need to create a new IAsyncResult. (We also need the AsyncState to match.) - IAsyncResult asyncResult; - if (task.IsCompleted) - { - // Synchronous completion. - asyncResult = new TaskWrapperAsyncResult(task, state, completedSynchronously: true); - callback?.Invoke(asyncResult); - } - else - { - // For asynchronous completion we need to schedule a callback. Whether we can use the Task as the IAsyncResult - // depends on whether the Task's AsyncState has reference equality with the requested state. - asyncResult = task.AsyncState == state ? (IAsyncResult)task : new TaskWrapperAsyncResult(task, state, completedSynchronously: false); - if (callback != null) - { - InvokeCallbackWhenTaskCompletes(task, callback, asyncResult); - } - } - return asyncResult; - } - - /// Processes an IAsyncResult returned by Begin. - /// The IAsyncResult to unwrap. - public static void End(IAsyncResult asyncResult) - { - Task task; - - // If the IAsyncResult is our task-wrapping IAsyncResult, extract the Task. - var twar = asyncResult as TaskWrapperAsyncResult; - if (twar != null) - { - task = twar.Task; - Debug.Assert(task != null, "TaskWrapperAsyncResult should never wrap a null Task."); - } - else - { - // Otherwise, the IAsyncResult should be a Task. - task = asyncResult as Task; - } - - // Make sure we actually got a task, then complete the operation by waiting on it. - if (task == null) - { - throw new ArgumentNullException(); - } - - task.GetAwaiter().GetResult(); - } - - /// Processes an IAsyncResult returned by Begin. - /// The IAsyncResult to unwrap. - public static TResult End(IAsyncResult asyncResult) - { - Task task; - - // If the IAsyncResult is our task-wrapping IAsyncResult, extract the Task. - var twar = asyncResult as TaskWrapperAsyncResult; - if (twar != null) - { - task = twar.Task as Task; - Debug.Assert(twar.Task != null, "TaskWrapperAsyncResult should never wrap a null Task."); - } - else - { - // Otherwise, the IAsyncResult should be a Task. - task = asyncResult as Task; - } - - // Make sure we actually got a task, then complete the operation by waiting on it. - if (task == null) - { - throw new ArgumentNullException(); - } - - return task.GetAwaiter().GetResult(); - } - - /// Invokes the callback asynchronously when the task has completed. - /// The Task to await. - /// The callback to invoke when the Task completes. - /// The Task used as the IAsyncResult. - private static void InvokeCallbackWhenTaskCompletes(Task antecedent, AsyncCallback callback, IAsyncResult asyncResult) - { - Debug.Assert(antecedent != null); - Debug.Assert(callback != null); - Debug.Assert(asyncResult != null); - - // We use OnCompleted rather than ContinueWith in order to avoid running synchronously - // if the task has already completed by the time we get here. This is separated out into - // its own method currently so that we only pay for the closure if necessary. - antecedent.ConfigureAwait(continueOnCapturedContext: false) - .GetAwaiter() - .OnCompleted(() => callback(asyncResult)); - - // PERFORMANCE NOTE: - // Assuming we're in the default ExecutionContext, the "slow path" of an incomplete - // task will result in four allocations: the new IAsyncResult, the delegate+closure - // in this method, and the continuation object inside of OnCompleted (necessary - // to capture both the Action delegate and the ExecutionContext in a single object). - // In the future, if performance requirements drove a need, those four - // allocations could be reduced to one. This would be achieved by having TaskWrapperAsyncResult - // also implement ITaskCompletionAction (and optionally IThreadPoolWorkItem). It would need - // additional fields to store the AsyncCallback and an ExecutionContext. Once configured, - // it would be set into the Task as a continuation. Its Invoke method would then be run when - // the antecedent completed, and, doing all of the necessary work to flow ExecutionContext, - // it would invoke the AsyncCallback. It could also have a field on it for the antecedent, - // so that the End method would have access to the completed antecedent. For related examples, - // see other implementations of ITaskCompletionAction, and in particular ReadWriteTask - // used in Stream.Begin/EndXx's implementation. - } - - /// - /// Provides a simple IAsyncResult that wraps a Task. This, in effect, allows - /// for overriding what's seen for the CompletedSynchronously and AsyncState values. - /// - private sealed class TaskWrapperAsyncResult : IAsyncResult - { - /// The wrapped Task. - internal readonly Task Task; - /// The new AsyncState value. - private readonly object _state; - /// The new CompletedSynchronously value. - private readonly bool _completedSynchronously; - - /// Initializes the IAsyncResult with the Task to wrap and the overriding AsyncState and CompletedSynchronously values. - /// The Task to wrap. - /// The new AsyncState value - /// The new CompletedSynchronously value. - internal TaskWrapperAsyncResult(Task task, object state, bool completedSynchronously) - { - Debug.Assert(task != null); - Debug.Assert(!completedSynchronously || task.IsCompleted, "If completedSynchronously is true, the task must be completed."); - - this.Task = task; - _state = state; - _completedSynchronously = completedSynchronously; - } - - // The IAsyncResult implementation. - // - IsCompleted and AsyncWaitHandle just pass through to the Task. - // - AsyncState and CompletedSynchronously return the corresponding values stored in this object. - - object IAsyncResult.AsyncState { get { return _state; } } - bool IAsyncResult.CompletedSynchronously { get { return _completedSynchronously; } } - bool IAsyncResult.IsCompleted { get { return this.Task.IsCompleted; } } - WaitHandle IAsyncResult.AsyncWaitHandle { get { return ((IAsyncResult)this.Task).AsyncWaitHandle; } } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index c35899a762..f13d43c931 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -706,7 +706,6 @@ - From 72a42907465ff6929a6c04300112ebec96f1b188 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Fri, 11 Apr 2025 17:50:28 -0500 Subject: [PATCH 3/4] Use TaskToAsyncResult for netcore Use eg from https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/interop-with-other-asynchronous-patterns-and-types for netfx --- .../Data/SqlClient/SqlSequentialStream.cs | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs index 2ac535a967..a8add56d54 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs @@ -300,12 +300,27 @@ public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, Asy throw ADP.ObjectDisposed(this); } - Task readTask = ReadAsync(buffer, offset, count, CancellationToken.None); - if (callback != null) - { - readTask.ContinueWith((t) => callback(t), TaskScheduler.Default); - } - return readTask; + var tcs = new TaskCompletionSource(state); + ReadAsync(buffer, offset, count, CancellationToken.None) + .ContinueWith(task => + { + if (task.IsFaulted) + { + tcs.TrySetException(task.Exception?.InnerException); + } + else if (task.IsCanceled) + { + tcs.TrySetCanceled(); + } + else + { + tcs.TrySetResult(task.Result); + } + + callback?.Invoke(tcs.Task); + }, TaskScheduler.Default); + + return tcs.Task; } public override int EndRead(IAsyncResult asyncResult) @@ -337,16 +352,11 @@ public override IAsyncResult BeginRead( object asyncState) { Task readTask = ReadAsync(array, offset, count, CancellationToken.None); - if (asyncCallback is not null) - { - readTask.ContinueWith(result => asyncCallback(result)); - } - - return readTask; + return TaskToAsyncResult.Begin(readTask, asyncCallback, asyncState); } public override int EndRead(IAsyncResult asyncResult) => - ((Task)asyncResult).Result; + TaskToAsyncResult.End(asyncResult); #endif } } From dd0b4cb1d8bac7cd9f5ab355be506e6271b1f844 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Wed, 16 Apr 2025 17:12:21 -0500 Subject: [PATCH 4/4] Reverting re-write of netfx begin implementation --- .../Data/SqlClient/SqlSequentialStream.cs | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs index a8add56d54..52b965325d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs @@ -300,27 +300,12 @@ public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, Asy throw ADP.ObjectDisposed(this); } - var tcs = new TaskCompletionSource(state); - ReadAsync(buffer, offset, count, CancellationToken.None) - .ContinueWith(task => - { - if (task.IsFaulted) - { - tcs.TrySetException(task.Exception?.InnerException); - } - else if (task.IsCanceled) - { - tcs.TrySetCanceled(); - } - else - { - tcs.TrySetResult(task.Result); - } - - callback?.Invoke(tcs.Task); - }, TaskScheduler.Default); - - return tcs.Task; + Task readTask = ReadAsync(buffer, offset, count, CancellationToken.None); + if (callback != null) + { + readTask.ContinueWith(t => callback(t), TaskScheduler.Default); + } + return readTask; } public override int EndRead(IAsyncResult asyncResult)