Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commite30c200

Browse files
authored
Use Task.WaitAsync in SemaphoreSlim.WaitAsync (#55262)
* Use Task.WaitAsync in SemaphoreSlim* Fix failing testThe Cancel_WaitAsync_ContinuationInvokedAsynchronously test was failing,highlighting that we were no longer invoking the continuationasynchronously from the Cancel call. But in fact we were incompletelydoing so in the past, such that we'd only force that asynchrony if notimeout was provided... if both a timeout and a token were provided,then we wouldn't. I've enhanced the test to validate both cases, andmade sure we now pass.
1 parent7f88911 commite30c200

File tree

3 files changed

+56
-29
lines changed

3 files changed

+56
-29
lines changed

‎src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs‎

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -701,35 +701,22 @@ private async Task<bool> WaitUntilCountOrTimeoutAsync(TaskNode asyncWaiter, int
701701
Debug.Assert(asyncWaiter!=null,"Waiter should have been constructed");
702702
Debug.Assert(Monitor.IsEntered(m_lockObjAndDisposed),"Requires the lock be held");
703703

704-
if(millisecondsTimeout!=Timeout.Infinite)
704+
awaitnewConfiguredNoThrowAwaiter<bool>(asyncWaiter.WaitAsync(TimeSpan.FromMilliseconds(millisecondsTimeout),cancellationToken));
705+
706+
if(cancellationToken.IsCancellationRequested)
705707
{
706-
// Wait until either the task is completed, cancellation is requested, or the timeout occurs.
707-
// We need to ensure that the Task.Delay task is appropriately cleaned up if the await
708-
// completes due to the asyncWaiter completing, so we use our own token that we can explicitly
709-
// cancel, and we chain the caller's supplied token into it.
710-
using(varcts=CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
711-
{
712-
if(asyncWaiter==awaitTask.WhenAny(asyncWaiter,Task.Delay(millisecondsTimeout,cts.Token)).ConfigureAwait(false))
713-
{
714-
cts.Cancel();// ensure that the Task.Delay task is cleaned up
715-
returntrue;// successfully acquired
716-
}
717-
}
708+
// If we might be running as part of a cancellation callback, force the completion to be asynchronous
709+
// so as to maintain semantics similar to when no token is passed (neither Release nor Cancel would invoke
710+
// continuations off of this task).
711+
awaitTaskScheduler.Default;
718712
}
719-
else// millisecondsTimeout == Timeout.Infinite
713+
714+
if(asyncWaiter.IsCompleted)
720715
{
721-
// Wait until either the task is completed or cancellation is requested.
722-
varcancellationTask=newTask(null,TaskCreationOptions.RunContinuationsAsynchronously,promiseStyle:true);
723-
using(cancellationToken.UnsafeRegister(static s=>((Task)s!).TrySetResult(),cancellationTask))
724-
{
725-
if(asyncWaiter==awaitTask.WhenAny(asyncWaiter,cancellationTask).ConfigureAwait(false))
726-
{
727-
returntrue;// successfully acquired
728-
}
729-
}
716+
returntrue;// successfully acquired
730717
}
731718

732-
//If we get here, the wait has timed out or been canceled.
719+
//The wait has timed out or been canceled.
733720

734721
// If the await completed synchronously, we still hold the lock. If it didn't,
735722
// we no longer hold the lock. As such, acquire it.
@@ -750,6 +737,19 @@ private async Task<bool> WaitUntilCountOrTimeoutAsync(TaskNode asyncWaiter, int
750737
returnawaitasyncWaiter.ConfigureAwait(false);
751738
}
752739

740+
// TODO https://github.com/dotnet/runtime/issues/22144: Replace with official nothrow await solution once available.
741+
/// <summary>Awaiter used to await a task.ConfigureAwait(false) but without throwing any exceptions for faulted or canceled tasks.</summary>
742+
privatereadonlystructConfiguredNoThrowAwaiter<T>:ICriticalNotifyCompletion
743+
{
744+
privatereadonlyTask<T>_task;
745+
publicConfiguredNoThrowAwaiter(Task<T>task)=>_task=task;
746+
publicConfiguredNoThrowAwaiter<T>GetAwaiter()=>this;
747+
publicboolIsCompleted=>_task.IsCompleted;
748+
publicvoidGetResult(){}
749+
publicvoidUnsafeOnCompleted(Actioncontinuation)=>_task.ConfigureAwait(false).GetAwaiter().UnsafeOnCompleted(continuation);
750+
publicvoidOnCompleted(Actioncontinuation)=>_task.ConfigureAwait(false).GetAwaiter().OnCompleted(continuation);
751+
}
752+
753753
/// <summary>
754754
/// Exits the <see cref="SemaphoreSlim"/> once.
755755
/// </summary>

‎src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskScheduler.cs‎

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -529,10 +529,32 @@ public SystemThreadingTasks_TaskSchedulerDebugView(TaskScheduler scheduler)
529529
// returns the scheduler's GetScheduledTasks
530530
publicIEnumerable<Task>?ScheduledTasks=>m_taskScheduler.GetScheduledTasks();
531531
}
532-
}
533-
534532

533+
// TODO https://github.com/dotnet/runtime/issues/20025: Consider exposing publicly.
534+
/// <summary>Gets an awaiter used to queue a continuation to this TaskScheduler.</summary>
535+
internalTaskSchedulerAwaiterGetAwaiter()=>newTaskSchedulerAwaiter(this);
535536

537+
/// <summary>Awaiter used to queue a continuation to a specified task scheduler.</summary>
538+
internalreadonlystructTaskSchedulerAwaiter:ICriticalNotifyCompletion
539+
{
540+
privatereadonlyTaskScheduler_scheduler;
541+
publicTaskSchedulerAwaiter(TaskSchedulerscheduler)=>_scheduler=scheduler;
542+
publicboolIsCompleted=>false;
543+
publicvoidGetResult(){}
544+
publicvoidOnCompleted(Actioncontinuation)=>Task.Factory.StartNew(continuation,CancellationToken.None,TaskCreationOptions.DenyChildAttach,_scheduler);
545+
publicvoidUnsafeOnCompleted(Actioncontinuation)
546+
{
547+
if(ReferenceEquals(_scheduler,Default))
548+
{
549+
ThreadPool.UnsafeQueueUserWorkItem(s=>s(),continuation,preferLocal:true);
550+
}
551+
else
552+
{
553+
OnCompleted(continuation);
554+
}
555+
}
556+
}
557+
}
536558

537559
/// <summary>
538560
/// A TaskScheduler implementation that executes all tasks queued to it through a call to

‎src/libraries/System.Threading/tests/SemaphoreSlimCancellationTests.cs‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,10 @@ public static void CancelAfterWait()
4949
// currently we don't expose this.. but it was verified manually
5050
}
5151

52-
[ConditionalFact(typeof(PlatformDetection),nameof(PlatformDetection.IsThreadingSupported))]
53-
publicstaticasyncTaskCancel_WaitAsync_ContinuationInvokedAsynchronously()
52+
[ConditionalTheory(typeof(PlatformDetection),nameof(PlatformDetection.IsThreadingSupported))]
53+
[InlineData(false)]
54+
[InlineData(true)]
55+
publicstaticasyncTaskCancel_WaitAsync_ContinuationInvokedAsynchronously(boolwithTimeout)
5456
{
5557
awaitTask.Run(async()=>// escape xunit's SynchronizationContext
5658
{
@@ -60,7 +62,10 @@ await Task.Run(async () => // escape xunit's SynchronizationContext
6062
varsentinel=newobject();
6163

6264
varsem=newSemaphoreSlim(0);
63-
Taskcontinuation=sem.WaitAsync(cts.Token).ContinueWith(prev=>
65+
TaskwaitTask=withTimeout?
66+
sem.WaitAsync(TimeSpan.FromDays(1),cts.Token):
67+
sem.WaitAsync(cts.Token);
68+
Taskcontinuation=waitTask.ContinueWith(prev=>
6469
{
6570
Assert.Equal(TaskStatus.Canceled,prev.Status);
6671
Assert.NotSame(sentinel,tl.Value);

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp