diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index 291fd8802d3016..61fd221b514aea 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -11,6 +11,7 @@ using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; +using System.Threading.Tasks.Sources; namespace System.Runtime.CompilerServices { @@ -134,9 +135,13 @@ public static partial class AsyncHelpers private struct RuntimeAsyncAwaitState { public Continuation? SentinelContinuation; + + // The following are the possible introducers of asynchrony into a chain of awaits. + // In other words - when we pend a chain of continuations it would be to one of these notifiers. public ICriticalNotifyCompletion? CriticalNotifier; public INotifyCompletion? Notifier; - public Task? CalledTask; + public IValueTaskSourceNotifier? ValueTaskSourceNotifier; + public Task? TaskNotifier; } [ThreadStatic] @@ -171,17 +176,35 @@ private static unsafe Continuation AllocContinuationClass(Continuation prevConti return newContinuation; } + /// + /// Used by internal thunks that implement awaiting on Task or a ValueTask. + /// A ValueTask may wrap: + /// - Completed result (we never await this) + /// - Task + /// - ValueTaskSource + /// Therefore, when we are awaiting a ValueTask completion we are really + /// awaiting a completion an underlying Task or ValueTaskSource. + /// + /// Task or a ValueTaskNotifier whose completion we are awaiting. [BypassReadyToRun] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] [RequiresPreviewFeatures] - private static void TransparentAwaitTask(Task t) + private static void TransparentAwait(object o) { ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; Continuation? sentinelContinuation = state.SentinelContinuation; if (sentinelContinuation == null) state.SentinelContinuation = sentinelContinuation = new Continuation(); - state.CalledTask = t; + if (o is Task t) + { + state.TaskNotifier = t; + } + else + { + state.ValueTaskSourceNotifier = (IValueTaskSourceNotifier)o; + } + AsyncSuspend(sentinelContinuation); } @@ -419,13 +442,16 @@ public static unsafe void DispatchContinuations(T task) where T : Task, public static void HandleSuspended(T task) where T : Task, ITaskCompletionAction where TOps : IRuntimeAsyncTaskOps { ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; + ICriticalNotifyCompletion? critNotifier = state.CriticalNotifier; INotifyCompletion? notifier = state.Notifier; - Task? calledTask = state.CalledTask; + IValueTaskSourceNotifier? vtsNotifier = state.ValueTaskSourceNotifier; + Task? taskNotifier = state.TaskNotifier; state.CriticalNotifier = null; state.Notifier = null; - state.CalledTask = null; + state.ValueTaskSourceNotifier = null; + state.TaskNotifier = null; Continuation sentinelContinuation = state.SentinelContinuation!; Continuation headContinuation = sentinelContinuation.Next!; @@ -447,16 +473,44 @@ public static void HandleSuspended(T task) where T : Task, ITaskComplet { critNotifier.UnsafeOnCompleted(TOps.GetContinuationAction(task)); } - else if (calledTask != null) + else if (taskNotifier != null) { // Runtime async callable wrapper for task returning // method. This implements the context transparent // forwarding and makes these wrappers minimal cost. - if (!calledTask.TryAddCompletionAction(task)) + if (!taskNotifier.TryAddCompletionAction(task)) { ThreadPool.UnsafeQueueUserWorkItemInternal(task, preferLocal: true); } } + else if (vtsNotifier != null) + { + // The awaiter must inform the source on whether the continuation wants to run on + // a context, although the source may decide to ignore the suggestion. + // Since the behavior of the source takes precedence, we clear the context flags of + // the awaiting continuation (so it will run transparently on what the source decides) + // and then tell the source if the awaiting frame prefers to continue on a context. + // The reason why we do it here and not when the notifier is created is because + // the continuation chain builds from the innermost frame out and at the time when the + // notifier is created we do not know yet if the caller wants to continue on a context. + ValueTaskSourceOnCompletedFlags configFlags = ValueTaskSourceOnCompletedFlags.None; + ContinuationFlags continuationFlags = headContinuation.Next!.Flags; + + const ContinuationFlags continueOnContextFlags = + ContinuationFlags.ContinueOnCapturedSynchronizationContext | + ContinuationFlags.ContinueOnCapturedTaskScheduler; + + if ((continuationFlags & continueOnContextFlags) != 0) + { + // if await has captured some context, inform the source + configFlags |= ValueTaskSourceOnCompletedFlags.UseSchedulingContext; + } + + // Clear continuation flags, so that continuation runs transparently + headContinuation.Next!.Flags &= ~continueFlags; + + vtsNotifier.OnCompleted(o => TOps.GetContinuationAction((T)o!).Invoke(), task, configFlags); + } else { Debug.Assert(notifier != null); diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index 3a1b3dc5fbe19c..754b462be0e7d2 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -714,7 +714,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableRiscV64Zbb, W("EnableRiscV64 #endif // Runtime-async -RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_RuntimeAsync, W("RuntimeAsync"), 0, "Enables runtime async method support") +RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_RuntimeAsync, W("RuntimeAsync"), 1, "Enables runtime async method support") /// /// Uncategorized diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index a44b6d92c0832c..e580e405f06524 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -1703,6 +1703,15 @@ void AsyncTransformation::CreateCheckAndSuspendAfterCall(BasicBlock* *remainder = m_comp->fgSplitBlockAfterNode(block, jtrue); JITDUMP(" Remainder is " FMT_BB "\n", (*remainder)->bbNum); + // HACK: Not sure why it can happen, but we may see the end IL for the block + // to increasing after splitting off its tail. + // This tweak is just to avoid asserts later on. + // This is not a real fix. + if (block->bbCodeOffsEnd > (*remainder)->bbCodeOffs) + { + block->bbCodeOffsEnd = (*remainder)->bbCodeOffs; + } + FlowEdge* retBBEdge = m_comp->fgAddRefPred(suspendBB, block); block->SetCond(retBBEdge, block->GetTargetEdge()); diff --git a/src/coreclr/vm/asyncthunks.cpp b/src/coreclr/vm/asyncthunks.cpp index fcb0c06d059936..519daeefeed193 100644 --- a/src/coreclr/vm/asyncthunks.cpp +++ b/src/coreclr/vm/asyncthunks.cpp @@ -475,27 +475,30 @@ void MethodDesc::EmitAsyncMethodThunk(MethodDesc* pAsyncOtherVariant, MetaSig& m _ASSERTE(!pAsyncOtherVariant->IsVoid()); // Implement IL that is effectively the following: - /* - { - Task task = other(arg); - if (!task.IsCompleted) - { - // Magic function which will suspend the current run of async methods - AsyncHelpers.TransparentAwaitTask(task); - } - return AsyncHelpers.CompletedTaskResult(task); - } + // { + // Task task = other(arg); + // if (!task.IsCompleted) + // { + // // Magic function which will suspend the current run of async methods + // AsyncHelpers.TransparentAwait(task); + // } + // return AsyncHelpers.CompletedTaskResult(task); + // } - For ValueTask: - { - ValueTask vt = other(arg); - if (vt.IsCompleted) - return vt.Result/vt.ThrowIfCompletedUnsuccessfully(); + // For ValueTask: - Task task = vt.AsTask(); - - } - */ + // { + // ValueTask vt = other(arg); + // if (!vt.IsCompleted) + // { + // taskOrNotifier = vt.AsTaskOrNotifier() + + // // Magic function which will suspend the current run of async methods + // AsyncHelpers.TransparentAwait(taskOrNotifier); + // } + + // return vt.Result/vt.ThrowIfCompletedUnsuccessfully(); + // } ILCodeStream* pCode = pSL->NewCodeStream(ILStubLinker::kDispatch); int userFuncToken; @@ -563,32 +566,27 @@ void MethodDesc::EmitAsyncMethodThunk(MethodDesc* pAsyncOtherVariant, MetaSig& m pCode->EmitLDARG(localArg++); } + // other(arg) pCode->EmitCALL(userFuncToken, localArg, 1); TypeHandle thLogicalRetType = msig.GetRetTypeHandleThrowing(); if (IsValueTaskAsyncThunk()) { - // Emit - // if (vtask.IsCompleted) - // return vtask.Result/vtask.ThrowIfCompletedUnsuccessfully() - // task = vtask.AsTask() - // - MethodTable* pMTValueTask; int isCompletedToken; int completionResultToken; - int asTaskToken; + int asTaskOrNotifierToken; if (msig.IsReturnTypeVoid()) { pMTValueTask = CoreLibBinder::GetClass(CLASS__VALUETASK); MethodDesc* pMDValueTaskIsCompleted = CoreLibBinder::GetMethod(METHOD__VALUETASK__GET_ISCOMPLETED); MethodDesc* pMDCompletionResult = CoreLibBinder::GetMethod(METHOD__VALUETASK__THROW_IF_COMPLETED_UNSUCCESSFULLY); - MethodDesc* pMDAsTask = CoreLibBinder::GetMethod(METHOD__VALUETASK__AS_TASK); + MethodDesc* pMDAsTaskOrNotifier = CoreLibBinder::GetMethod(METHOD__VALUETASK__AS_TASK_OR_NOTIFIER); isCompletedToken = pCode->GetToken(pMDValueTaskIsCompleted); completionResultToken = pCode->GetToken(pMDCompletionResult); - asTaskToken = pCode->GetToken(pMDAsTask); + asTaskOrNotifierToken = pCode->GetToken(pMDAsTaskOrNotifier); } else { @@ -597,73 +595,75 @@ void MethodDesc::EmitAsyncMethodThunk(MethodDesc* pAsyncOtherVariant, MetaSig& m MethodDesc* pMDValueTaskIsCompleted = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__GET_ISCOMPLETED); MethodDesc* pMDCompletionResult = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__GET_RESULT); - MethodDesc* pMDAsTask = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__AS_TASK); + MethodDesc* pMDAsTaskOrNotifier = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__AS_TASK_OR_NOTIFIER); pMDValueTaskIsCompleted = FindOrCreateAssociatedMethodDesc(pMDValueTaskIsCompleted, pMTValueTask, FALSE, Instantiation(), FALSE); pMDCompletionResult = FindOrCreateAssociatedMethodDesc(pMDCompletionResult, pMTValueTask, FALSE, Instantiation(), FALSE); - pMDAsTask = FindOrCreateAssociatedMethodDesc(pMDAsTask, pMTValueTask, FALSE, Instantiation(), FALSE); + pMDAsTaskOrNotifier = FindOrCreateAssociatedMethodDesc(pMDAsTaskOrNotifier, pMTValueTask, FALSE, Instantiation(), FALSE); isCompletedToken = GetTokenForGenericTypeMethodCallWithAsyncReturnType(pCode, pMDValueTaskIsCompleted); completionResultToken = GetTokenForGenericTypeMethodCallWithAsyncReturnType(pCode, pMDCompletionResult); - asTaskToken = GetTokenForGenericTypeMethodCallWithAsyncReturnType(pCode, pMDAsTask); + asTaskOrNotifierToken = GetTokenForGenericTypeMethodCallWithAsyncReturnType(pCode, pMDAsTaskOrNotifier); } LocalDesc valueTaskLocalDesc(pMTValueTask); DWORD valueTaskLocal = pCode->NewLocal(valueTaskLocalDesc); - ILCodeLabel* valueTaskNotCompletedLabel = pCode->NewCodeLabel(); + ILCodeLabel* valueTaskCompletedLabel = pCode->NewCodeLabel(); // Store value task returned by call to actual user func pCode->EmitSTLOC(valueTaskLocal); - pCode->EmitLDLOCA(valueTaskLocal); pCode->EmitCALL(isCompletedToken, 1, 1); - pCode->EmitBRFALSE(valueTaskNotCompletedLabel); + pCode->EmitBRTRUE(valueTaskCompletedLabel); pCode->EmitLDLOCA(valueTaskLocal); - pCode->EmitCALL(completionResultToken, 1, msig.IsReturnTypeVoid() ? 0 : 1); - pCode->EmitRET(); + pCode->EmitCALL(asTaskOrNotifierToken, 1, 1); + pCode->EmitCALL(METHOD__ASYNC_HELPERS__TRANSPARENT_AWAIT, 1, 0); - pCode->EmitLabel(valueTaskNotCompletedLabel); + pCode->EmitLabel(valueTaskCompletedLabel); pCode->EmitLDLOCA(valueTaskLocal); - pCode->EmitCALL(asTaskToken, 1, 1); - } - - MethodTable* pMTTask; - - int completedTaskResultToken; - if (msig.IsReturnTypeVoid()) - { - pMTTask = CoreLibBinder::GetClass(CLASS__TASK); - - MethodDesc* pMDCompletedTask = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__COMPLETED_TASK); - completedTaskResultToken = pCode->GetToken(pMDCompletedTask); + pCode->EmitCALL(completionResultToken, 1, msig.IsReturnTypeVoid() ? 0 : 1); + pCode->EmitRET(); } else { - MethodTable* pMTTaskOpen = CoreLibBinder::GetClass(CLASS__TASK_1); - pMTTask = ClassLoader::LoadGenericInstantiationThrowing(pMTTaskOpen->GetModule(), pMTTaskOpen->GetCl(), Instantiation(&thLogicalRetType, 1)).GetMethodTable(); + MethodTable* pMTTask; - MethodDesc* pMDCompletedTaskResult = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__COMPLETED_TASK_RESULT); - pMDCompletedTaskResult = FindOrCreateAssociatedMethodDesc(pMDCompletedTaskResult, pMDCompletedTaskResult->GetMethodTable(), FALSE, Instantiation(&thLogicalRetType, 1), FALSE); - completedTaskResultToken = GetTokenForGenericMethodCallWithAsyncReturnType(pCode, pMDCompletedTaskResult); - } + int completedTaskResultToken; + if (msig.IsReturnTypeVoid()) + { + pMTTask = CoreLibBinder::GetClass(CLASS__TASK); - LocalDesc taskLocalDesc(pMTTask); - DWORD taskLocal = pCode->NewLocal(taskLocalDesc); - ILCodeLabel* pGetResultLabel = pCode->NewCodeLabel(); + MethodDesc* pMDCompletedTask = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__COMPLETED_TASK); + completedTaskResultToken = pCode->GetToken(pMDCompletedTask); + } + else + { + MethodTable* pMTTaskOpen = CoreLibBinder::GetClass(CLASS__TASK_1); + pMTTask = ClassLoader::LoadGenericInstantiationThrowing(pMTTaskOpen->GetModule(), pMTTaskOpen->GetCl(), Instantiation(&thLogicalRetType, 1)).GetMethodTable(); - // Store task returned by actual user func or by ValueTask.AsTask - pCode->EmitSTLOC(taskLocal); + MethodDesc* pMDCompletedTaskResult = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__COMPLETED_TASK_RESULT); + pMDCompletedTaskResult = FindOrCreateAssociatedMethodDesc(pMDCompletedTaskResult, pMDCompletedTaskResult->GetMethodTable(), FALSE, Instantiation(&thLogicalRetType, 1), FALSE); + completedTaskResultToken = GetTokenForGenericMethodCallWithAsyncReturnType(pCode, pMDCompletedTaskResult); + } - pCode->EmitLDLOC(taskLocal); - pCode->EmitCALL(METHOD__TASK__GET_ISCOMPLETED, 1, 1); - pCode->EmitBRTRUE(pGetResultLabel); + LocalDesc taskLocalDesc(pMTTask); + DWORD taskLocal = pCode->NewLocal(taskLocalDesc); + ILCodeLabel* pGetResultLabel = pCode->NewCodeLabel(); - pCode->EmitLDLOC(taskLocal); - pCode->EmitCALL(METHOD__ASYNC_HELPERS__TRANSPARENT_AWAIT_TASK, 1, 0); + // Store task returned by actual user func or by ValueTask.AsTask + pCode->EmitSTLOC(taskLocal); - pCode->EmitLabel(pGetResultLabel); - pCode->EmitLDLOC(taskLocal); - pCode->EmitCALL(completedTaskResultToken, 1, msig.IsReturnTypeVoid() ? 0 : 1); - pCode->EmitRET(); + pCode->EmitLDLOC(taskLocal); + pCode->EmitCALL(METHOD__TASK__GET_ISCOMPLETED, 1, 1); + pCode->EmitBRTRUE(pGetResultLabel); + + pCode->EmitLDLOC(taskLocal); + pCode->EmitCALL(METHOD__ASYNC_HELPERS__TRANSPARENT_AWAIT, 1, 0); + + pCode->EmitLabel(pGetResultLabel); + pCode->EmitLDLOC(taskLocal); + pCode->EmitCALL(completedTaskResultToken, 1, msig.IsReturnTypeVoid() ? 0 : 1); + pCode->EmitRET(); + } } diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 193358e11af195..88aaaabfeb4eba 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -346,7 +346,7 @@ DEFINE_METHOD(THREAD_START_EXCEPTION,EX_CTOR, .ctor, DEFINE_CLASS(VALUETASK_1, Tasks, ValueTask`1) DEFINE_METHOD(VALUETASK_1, GET_ISCOMPLETED, get_IsCompleted, NoSig) DEFINE_METHOD(VALUETASK_1, GET_RESULT, get_Result, NoSig) -DEFINE_METHOD(VALUETASK_1, AS_TASK, AsTask, IM_RetTaskOfT) +DEFINE_METHOD(VALUETASK_1, AS_TASK_OR_NOTIFIER, AsTaskOrNotifier, IM_RetObj) DEFINE_CLASS(VALUETASK, Tasks, ValueTask) DEFINE_METHOD(VALUETASK, FROM_EXCEPTION, FromException, SM_Exception_RetValueTask) @@ -355,7 +355,7 @@ DEFINE_METHOD(VALUETASK, FROM_RESULT_T, FromResult, GM_T_RetValueTaskOfT) DEFINE_METHOD(VALUETASK, GET_COMPLETED_TASK, get_CompletedTask, SM_RetValueTask) DEFINE_METHOD(VALUETASK, GET_ISCOMPLETED, get_IsCompleted, NoSig) DEFINE_METHOD(VALUETASK, THROW_IF_COMPLETED_UNSUCCESSFULLY, ThrowIfCompletedUnsuccessfully, NoSig) -DEFINE_METHOD(VALUETASK, AS_TASK, AsTask, IM_RetTask) +DEFINE_METHOD(VALUETASK, AS_TASK_OR_NOTIFIER, AsTaskOrNotifier, IM_RetObj) DEFINE_CLASS(TASK_1, Tasks, Task`1) DEFINE_METHOD(TASK_1, GET_RESULTONSUCCESS, get_ResultOnSuccess, NoSig) @@ -741,9 +741,9 @@ DEFINE_METHOD(ASYNC_HELPERS, VALUETASK_FROM_EXCEPTION, ValueTaskFromExcepti DEFINE_METHOD(ASYNC_HELPERS, VALUETASK_FROM_EXCEPTION_1, ValueTaskFromException, GM_Exception_RetValueTaskOfT) DEFINE_METHOD(ASYNC_HELPERS, UNSAFE_AWAIT_AWAITER_1, UnsafeAwaitAwaiter, GM_T_RetVoid) -DEFINE_METHOD(ASYNC_HELPERS, TRANSPARENT_AWAIT_TASK, TransparentAwaitTask, NoSig) -DEFINE_METHOD(ASYNC_HELPERS, COMPLETED_TASK_RESULT, CompletedTaskResult, NoSig) -DEFINE_METHOD(ASYNC_HELPERS, COMPLETED_TASK, CompletedTask, NoSig) +DEFINE_METHOD(ASYNC_HELPERS, TRANSPARENT_AWAIT, TransparentAwait, NoSig) +DEFINE_METHOD(ASYNC_HELPERS, COMPLETED_TASK_RESULT, CompletedTaskResult, NoSig) +DEFINE_METHOD(ASYNC_HELPERS, COMPLETED_TASK, CompletedTask, NoSig) DEFINE_METHOD(ASYNC_HELPERS, CAPTURE_EXECUTION_CONTEXT, CaptureExecutionContext, NoSig) DEFINE_METHOD(ASYNC_HELPERS, RESTORE_EXECUTION_CONTEXT, RestoreExecutionContext, NoSig) DEFINE_METHOD(ASYNC_HELPERS, CAPTURE_CONTINUATION_CONTEXT, CaptureContinuationContext, NoSig) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs index 90f86c68159c28..692cfbb0a32cc3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs @@ -179,6 +179,30 @@ obj as Task ?? GetTaskForValueTaskSource(Unsafe.As(obj)); } + internal object AsTaskOrNotifier() + { + object? obj = _obj; + Debug.Assert(obj is Task || obj is IValueTaskSource); + return + obj as Task ?? + (object)new ValueTaskSourceNotifier(Unsafe.As(obj), _token); + } + + private sealed class ValueTaskSourceNotifier : IValueTaskSourceNotifier + { + private IValueTaskSource _valueTaskSource; + private short _token; + + public ValueTaskSourceNotifier(IValueTaskSource valueTaskSource, short token) + { + _valueTaskSource = valueTaskSource; + _token = token; + } + + public void OnCompleted(Action continuation, object? state, ValueTaskSourceOnCompletedFlags flags) => + _valueTaskSource.OnCompleted(continuation, state, _token, flags); + } + /// Gets a that may be used at any point in the future. public ValueTask Preserve() => _obj == null ? this : new ValueTask(AsTask()); @@ -588,6 +612,30 @@ public Task AsTask() return GetTaskForValueTaskSource(Unsafe.As>(obj)); } + internal object AsTaskOrNotifier() + { + object? obj = _obj; + Debug.Assert(obj is Task || obj is IValueTaskSource); + return + obj as Task ?? + (object)new ValueTaskSourceNotifier(Unsafe.As>(obj), _token); + } + + private sealed class ValueTaskSourceNotifier : IValueTaskSourceNotifier + { + private IValueTaskSource _valueTaskSource; + private short _token; + + public ValueTaskSourceNotifier(IValueTaskSource valueTaskSource, short token) + { + _valueTaskSource = valueTaskSource; + _token = token; + } + + public void OnCompleted(Action continuation, object? state, ValueTaskSourceOnCompletedFlags flags) => + _valueTaskSource.OnCompleted(continuation, state, _token, flags); + } + /// Gets a that may be used at any point in the future. public ValueTask Preserve() => _obj == null ? this : new ValueTask(AsTask()); @@ -848,4 +896,9 @@ public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCaptu return string.Empty; } } + + internal interface IValueTaskSourceNotifier + { + void OnCompleted(Action continuation, object? state, ValueTaskSourceOnCompletedFlags flags); + } } diff --git a/src/tests/async/Directory.Build.targets b/src/tests/async/Directory.Build.targets index 5a4d413a9e1f62..61a1b2822ed114 100644 --- a/src/tests/async/Directory.Build.targets +++ b/src/tests/async/Directory.Build.targets @@ -4,10 +4,5 @@ true - - - true - - diff --git a/src/tests/async/valuetaskSource/valuetaskSource.cs b/src/tests/async/valuetaskSource/valuetaskSource.cs new file mode 100644 index 00000000000000..3bf080a1f93a09 --- /dev/null +++ b/src/tests/async/valuetaskSource/valuetaskSource.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks.Sources; +using Xunit; + +public class Async2ValueTaskSource +{ + static string trace; + + [Fact] + static void TestEntry() + { + ValueTask vtUseContext = new ValueTask(new Source(true), 1); + + AwaitConfigDefault(vtUseContext).GetAwaiter().GetResult(); + Assert.Equal("UseSchedulingContext", trace); + AwaitConfigTrue(vtUseContext).GetAwaiter().GetResult(); + Assert.Equal("UseSchedulingContext", trace); + AwaitConfigFalse(vtUseContext).GetAwaiter().GetResult(); + Assert.Equal("None", trace); + + ValueTask vtIgnoreContext = new ValueTask(new Source(true), 1); + AwaitConfigDefault(vtIgnoreContext).GetAwaiter().GetResult(); + Assert.Equal("UseSchedulingContext", trace); + AwaitConfigTrue(vtIgnoreContext).GetAwaiter().GetResult(); + Assert.Equal("UseSchedulingContext", trace); + AwaitConfigFalse(vtIgnoreContext).GetAwaiter().GetResult(); + Assert.Equal("None", trace); + + Console.WriteLine(); + SynchronizationContext.SetSynchronizationContext(new SideeffectingContext()); + + AwaitConfigDefault(vtUseContext).GetAwaiter().GetResult(); + Assert.Equal("UseSchedulingContext Posted", trace); + AwaitConfigTrue(vtUseContext).GetAwaiter().GetResult(); + Assert.Equal("UseSchedulingContext Posted", trace); + AwaitConfigFalse(vtUseContext).GetAwaiter().GetResult(); + Assert.Equal("None Posted", trace); + + AwaitConfigDefault(vtIgnoreContext).GetAwaiter().GetResult(); + Assert.Equal("UseSchedulingContext Posted", trace); + AwaitConfigTrue(vtIgnoreContext).GetAwaiter().GetResult(); + Assert.Equal("UseSchedulingContext Posted", trace); + AwaitConfigFalse(vtIgnoreContext).GetAwaiter().GetResult(); + Assert.Equal("None Posted", trace); + } + + class SideeffectingContext : SynchronizationContext + { + public override void Post(SendOrPostCallback d, object? state) + { + trace += " Posted"; + base.Post(d, state); + } + } + + static async ValueTask AwaitConfigDefault(ValueTask vt) + { + await (vt); + } + + static async ValueTask AwaitConfigTrue(ValueTask vt) + { + await (vt).ConfigureAwait(true); + } + + static async ValueTask AwaitConfigFalse(ValueTask vt) + { + await (vt).ConfigureAwait(false); + } + + class Source : IValueTaskSource + { + private readonly bool _useContext; + public Source(bool useContext) + { + _useContext = useContext; + } + + public void GetResult(short token) + { + } + + public ValueTaskSourceStatus GetStatus(short token) + { + return ValueTaskSourceStatus.Pending; + } + + public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) + { + trace = flags.ToString(); + + var schedulingContext = SynchronizationContext.Current; + if (_useContext && schedulingContext != null) + { + schedulingContext.Post((obj) => continuation(obj), state); + } + else + { + ThreadPool.QueueUserWorkItem((obj) => continuation(obj), state); + } + } + } +} diff --git a/src/tests/async/valuetaskSource/valuetaskSource.csproj b/src/tests/async/valuetaskSource/valuetaskSource.csproj new file mode 100644 index 00000000000000..9367a79b2edbb1 --- /dev/null +++ b/src/tests/async/valuetaskSource/valuetaskSource.csproj @@ -0,0 +1,8 @@ + + + True + + + + +