Skip to content

Commit 5ad464f

Browse files
committed
Fix for configured ValueTask sources
1 parent 14f073b commit 5ad464f

File tree

4 files changed

+131
-17
lines changed

4 files changed

+131
-17
lines changed

src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Runtime.Versioning;
1111
using System.Threading;
1212
using System.Threading.Tasks;
13+
using System.Threading.Tasks.Sources;
1314

1415
namespace System.Runtime.CompilerServices
1516
{
@@ -491,6 +492,49 @@ public static void HandleSuspended<T, TOps>(T task) where T : Task, ITaskComplet
491492
}
492493
else if (calledTask != null)
493494
{
495+
if (calledTask is IValueTaskAsTask vtTask)
496+
{
497+
if (!vtTask.IsConfigured)
498+
{
499+
// ValueTaskSource can be configured to use scheduling context or to ignore it.
500+
// The awaiter must inform the source whether it wants to continue on a context,
501+
// but the source may decide to ignore the suggestion. Since the behavior of
502+
// the source takes precedence, we clear the context flags from the awaiting
503+
// continuation (so it will run transparently on what source decides) and tell
504+
// the source that awaiting frame prefers to continue on a context.
505+
// The reason why we do it here is because the continuation chain builds from the
506+
// innermost frame out and when the leaf thunk links the head continuation,
507+
// it does not know if the caller wants to continue in a context. Thus the thunk
508+
// creates an "unconfigured" IValueTaskAsTask and we configure it here.
509+
ValueTaskSourceOnCompletedFlags configFlags = ValueTaskSourceOnCompletedFlags.None;
510+
CorInfoContinuationFlags continuationFlags = headContinuation.Next!.Flags;
511+
512+
const CorInfoContinuationFlags continueOnContextFlags =
513+
CorInfoContinuationFlags.CORINFO_CONTINUATION_CONTINUE_ON_CAPTURED_SYNCHRONIZATION_CONTEXT |
514+
CorInfoContinuationFlags.CORINFO_CONTINUATION_CONTINUE_ON_CAPTURED_TASK_SCHEDULER;
515+
516+
if ((continuationFlags & continueOnContextFlags) != 0)
517+
{
518+
// effectively move context flags from headContinuation to the source config.
519+
configFlags |= ValueTaskSourceOnCompletedFlags.UseSchedulingContext;
520+
headContinuation.Next!.Flags &= ~continueOnContextFlags;
521+
}
522+
523+
vtTask.Configure(configFlags);
524+
525+
if (!calledTask.TryAddCompletionAction(task))
526+
{
527+
// calledTask has already completed and we need to schedule
528+
// our code for execution ourselves.
529+
// Restore the continuation flags before doing that.
530+
headContinuation.Next!.Flags = continuationFlags;
531+
ThreadPool.UnsafeQueueUserWorkItemInternal(task, preferLocal: true);
532+
}
533+
534+
return;
535+
}
536+
}
537+
494538
// Runtime async callable wrapper for task returning
495539
// method. This implements the context transparent
496540
// forwarding and makes these wrappers minimal cost.

src/coreclr/vm/asyncthunks.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ void MethodDesc::EmitAsyncMethodThunk(MethodDesc* pAsyncOtherVariant, MetaSig& m
591591

592592
MethodDesc* pMDValueTaskIsCompleted = CoreLibBinder::GetMethod(METHOD__VALUETASK__GET_ISCOMPLETED);
593593
MethodDesc* pMDCompletionResult = CoreLibBinder::GetMethod(METHOD__VALUETASK__THROW_IF_COMPLETED_UNSUCCESSFULLY);
594-
MethodDesc* pMDAsTask = CoreLibBinder::GetMethod(METHOD__VALUETASK__AS_TASK);
594+
MethodDesc* pMDAsTask = CoreLibBinder::GetMethod(METHOD__VALUETASK__AS_UNCONFIGURED_TASK);
595595

596596
isCompletedToken = pCode->GetToken(pMDValueTaskIsCompleted);
597597
completionResultToken = pCode->GetToken(pMDCompletionResult);
@@ -604,7 +604,7 @@ void MethodDesc::EmitAsyncMethodThunk(MethodDesc* pAsyncOtherVariant, MetaSig& m
604604

605605
MethodDesc* pMDValueTaskIsCompleted = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__GET_ISCOMPLETED);
606606
MethodDesc* pMDCompletionResult = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__GET_RESULT);
607-
MethodDesc* pMDAsTask = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__AS_TASK);
607+
MethodDesc* pMDAsTask = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__AS_UNCONFIGURED_TASK);
608608

609609
pMDValueTaskIsCompleted = FindOrCreateAssociatedMethodDesc(pMDValueTaskIsCompleted, pMTValueTask, FALSE, Instantiation(), FALSE);
610610
pMDCompletionResult = FindOrCreateAssociatedMethodDesc(pMDCompletionResult, pMTValueTask, FALSE, Instantiation(), FALSE);

src/coreclr/vm/corelib.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ DEFINE_METHOD(THREAD_START_EXCEPTION,EX_CTOR, .ctor,
346346
DEFINE_CLASS(VALUETASK_1, Tasks, ValueTask`1)
347347
DEFINE_METHOD(VALUETASK_1, GET_ISCOMPLETED, get_IsCompleted, NoSig)
348348
DEFINE_METHOD(VALUETASK_1, GET_RESULT, get_Result, NoSig)
349-
DEFINE_METHOD(VALUETASK_1, AS_TASK, AsTask, IM_RetTaskOfT)
349+
DEFINE_METHOD(VALUETASK_1, AS_UNCONFIGURED_TASK, AsUnconfiguredTask, IM_RetTaskOfT)
350350

351351
DEFINE_CLASS(VALUETASK, Tasks, ValueTask)
352352
DEFINE_METHOD(VALUETASK, FROM_EXCEPTION, FromException, SM_Exception_RetValueTask)
@@ -355,7 +355,7 @@ DEFINE_METHOD(VALUETASK, FROM_RESULT_T, FromResult, GM_T_RetValueTaskOfT)
355355
DEFINE_METHOD(VALUETASK, GET_COMPLETED_TASK, get_CompletedTask, SM_RetValueTask)
356356
DEFINE_METHOD(VALUETASK, GET_ISCOMPLETED, get_IsCompleted, NoSig)
357357
DEFINE_METHOD(VALUETASK, THROW_IF_COMPLETED_UNSUCCESSFULLY, ThrowIfCompletedUnsuccessfully, NoSig)
358-
DEFINE_METHOD(VALUETASK, AS_TASK, AsTask, IM_RetTask)
358+
DEFINE_METHOD(VALUETASK, AS_UNCONFIGURED_TASK, AsUnconfiguredTask, IM_RetTask)
359359

360360
DEFINE_CLASS(TASK_1, Tasks, Task`1)
361361
DEFINE_METHOD(TASK_1, GET_RESULTONSUCCESS, get_ResultOnSuccess, NoSig)

src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs

Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,17 @@ public Task AsTask()
176176
return
177177
obj == null ? Task.CompletedTask :
178178
obj as Task ??
179-
GetTaskForValueTaskSource(Unsafe.As<IValueTaskSource>(obj));
179+
GetTaskForValueTaskSource(Unsafe.As<IValueTaskSource>(obj), ValueTaskSourceOnCompletedFlags.None);
180+
}
181+
182+
internal Task AsUnconfiguredTask()
183+
{
184+
object? obj = _obj;
185+
Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource);
186+
return
187+
obj == null ? Task.CompletedTask :
188+
obj as Task ??
189+
GetTaskForValueTaskSource(Unsafe.As<IValueTaskSource>(obj), IValueTaskAsTask._unconfigured);
180190
}
181191

182192
/// <summary>Gets a <see cref="ValueTask"/> that may be used at any point in the future.</summary>
@@ -187,7 +197,7 @@ obj as Task ??
187197
/// The <see cref="IValueTaskSource"/> is passed in rather than reading and casting <see cref="_obj"/>
188198
/// so that the caller can pass in an object it's already validated.
189199
/// </remarks>
190-
private Task GetTaskForValueTaskSource(IValueTaskSource t)
200+
private Task GetTaskForValueTaskSource(IValueTaskSource t, ValueTaskSourceOnCompletedFlags flags)
191201
{
192202
ValueTaskSourceStatus status = t.GetStatus(_token);
193203
if (status != ValueTaskSourceStatus.Pending)
@@ -225,11 +235,11 @@ private Task GetTaskForValueTaskSource(IValueTaskSource t)
225235
}
226236
}
227237

228-
return new ValueTaskSourceAsTask(t, _token);
238+
return new ValueTaskSourceAsTask(t, _token, flags);
229239
}
230240

231241
/// <summary>Type used to create a <see cref="Task"/> to represent a <see cref="IValueTaskSource"/>.</summary>
232-
private sealed class ValueTaskSourceAsTask : Task
242+
private sealed class ValueTaskSourceAsTask : Task, IValueTaskAsTask
233243
{
234244
private static readonly Action<object?> s_completionAction = static state =>
235245
{
@@ -283,11 +293,28 @@ state is ValueTaskSourceAsTask vsts ?
283293
/// <summary>The token to pass through to operations on <see cref="_source"/></summary>
284294
private readonly short _token;
285295

286-
internal ValueTaskSourceAsTask(IValueTaskSource source, short token)
296+
private bool _configured;
297+
298+
bool IValueTaskAsTask.IsConfigured => _configured;
299+
void IValueTaskAsTask.Configure(ValueTaskSourceOnCompletedFlags flags) => Configure(flags);
300+
301+
private void Configure(ValueTaskSourceOnCompletedFlags flags)
302+
{
303+
Debug.Assert(!_configured);
304+
305+
_configured = true;
306+
_source!.OnCompleted(s_completionAction, this, _token, flags);
307+
}
308+
309+
internal ValueTaskSourceAsTask(IValueTaskSource source, short token, ValueTaskSourceOnCompletedFlags flags)
287310
{
288311
_token = token;
289312
_source = source;
290-
source.OnCompleted(s_completionAction, this, token, ValueTaskSourceOnCompletedFlags.None);
313+
314+
if (flags != IValueTaskAsTask._unconfigured)
315+
{
316+
Configure(flags);
317+
}
291318
}
292319
}
293320

@@ -585,7 +612,25 @@ public Task<TResult> AsTask()
585612
return t;
586613
}
587614

588-
return GetTaskForValueTaskSource(Unsafe.As<IValueTaskSource<TResult>>(obj));
615+
return GetTaskForValueTaskSource(Unsafe.As<IValueTaskSource<TResult>>(obj), ValueTaskSourceOnCompletedFlags.None);
616+
}
617+
618+
internal Task<TResult> AsUnconfiguredTask()
619+
{
620+
object? obj = _obj;
621+
Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>);
622+
623+
if (obj == null)
624+
{
625+
return Task.FromResult(_result!);
626+
}
627+
628+
if (obj is Task<TResult> t)
629+
{
630+
return t;
631+
}
632+
633+
return GetTaskForValueTaskSource(Unsafe.As<IValueTaskSource<TResult>>(obj), IValueTaskAsTask._unconfigured);
589634
}
590635

591636
/// <summary>Gets a <see cref="ValueTask{TResult}"/> that may be used at any point in the future.</summary>
@@ -596,7 +641,7 @@ public Task<TResult> AsTask()
596641
/// The <see cref="IValueTaskSource{TResult}"/> is passed in rather than reading and casting <see cref="_obj"/>
597642
/// so that the caller can pass in an object it's already validated.
598643
/// </remarks>
599-
private Task<TResult> GetTaskForValueTaskSource(IValueTaskSource<TResult> t)
644+
private Task<TResult> GetTaskForValueTaskSource(IValueTaskSource<TResult> t, ValueTaskSourceOnCompletedFlags flags)
600645
{
601646
ValueTaskSourceStatus status = t.GetStatus(_token);
602647
if (status != ValueTaskSourceStatus.Pending)
@@ -633,11 +678,11 @@ private Task<TResult> GetTaskForValueTaskSource(IValueTaskSource<TResult> t)
633678
}
634679
}
635680

636-
return new ValueTaskSourceAsTask(t, _token);
681+
return new ValueTaskSourceAsTask(t, _token, flags);
637682
}
638683

639684
/// <summary>Type used to create a <see cref="Task{TResult}"/> to represent a <see cref="IValueTaskSource{TResult}"/>.</summary>
640-
private sealed class ValueTaskSourceAsTask : Task<TResult>
685+
private sealed class ValueTaskSourceAsTask : Task<TResult>, IValueTaskAsTask
641686
{
642687
private static readonly Action<object?> s_completionAction = static state =>
643688
{
@@ -690,11 +735,28 @@ state is ValueTaskSourceAsTask vsts ?
690735
/// <summary>The token to pass through to operations on <see cref="_source"/></summary>
691736
private readonly short _token;
692737

693-
public ValueTaskSourceAsTask(IValueTaskSource<TResult> source, short token)
738+
private bool _configured;
739+
740+
bool IValueTaskAsTask.IsConfigured => _configured;
741+
void IValueTaskAsTask.Configure(ValueTaskSourceOnCompletedFlags flags) => Configure(flags);
742+
743+
private void Configure(ValueTaskSourceOnCompletedFlags flags)
744+
{
745+
Debug.Assert(!_configured);
746+
747+
_configured = true;
748+
_source!.OnCompleted(s_completionAction, this, _token, flags);
749+
}
750+
751+
internal ValueTaskSourceAsTask(IValueTaskSource<TResult> source, short token, ValueTaskSourceOnCompletedFlags flags)
694752
{
695-
_source = source;
696753
_token = token;
697-
source.OnCompleted(s_completionAction, this, token, ValueTaskSourceOnCompletedFlags.None);
754+
_source = source;
755+
756+
if (flags != IValueTaskAsTask._unconfigured)
757+
{
758+
Configure(flags);
759+
}
698760
}
699761
}
700762

@@ -848,4 +910,12 @@ public ConfiguredValueTaskAwaitable<TResult> ConfigureAwait(bool continueOnCaptu
848910
return string.Empty;
849911
}
850912
}
913+
914+
internal interface IValueTaskAsTask
915+
{
916+
internal const ValueTaskSourceOnCompletedFlags _unconfigured = (ValueTaskSourceOnCompletedFlags)(-1);
917+
918+
bool IsConfigured { get; }
919+
void Configure(ValueTaskSourceOnCompletedFlags flags);
920+
}
851921
}

0 commit comments

Comments
 (0)