@@ -39,7 +39,7 @@ internal static class TaskExtensions
3939 {
4040#if ! NET6_0_OR_GREATER
4141 private static readonly TaskContinuationOptions s_tco = TaskContinuationOptions . OnlyOnFaulted | TaskContinuationOptions . ExecuteSynchronously ;
42- private static void continuation ( Task t , object s ) => t . Exception . Handle ( e => true ) ;
42+ private static void IgnoreTaskContinuation ( Task t , object s ) => t . Exception . Handle ( e => true ) ;
4343#endif
4444
4545 public static Task TimeoutAfter ( this Task task , TimeSpan timeout )
@@ -59,60 +59,112 @@ public static Task TimeoutAfter(this Task task, TimeSpan timeout)
5959
6060 return DoTimeoutAfter ( task , timeout ) ;
6161
62+ // https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#using-a-timeout
6263 static async Task DoTimeoutAfter ( Task task , TimeSpan timeout )
6364 {
64- if ( task == await Task . WhenAny ( task , Task . Delay ( timeout ) ) . ConfigureAwait ( false ) )
65+ using ( var cts = new CancellationTokenSource ( ) )
6566 {
67+ Task delayTask = Task . Delay ( timeout , cts . Token ) ;
68+ Task resultTask = await Task . WhenAny ( task , delayTask ) . ConfigureAwait ( false ) ;
69+ if ( resultTask == delayTask )
70+ {
71+ task . Ignore ( ) ;
72+ throw new TimeoutException ( ) ;
73+ }
74+ else
75+ {
76+ cts . Cancel ( ) ;
77+ }
78+
6679 await task . ConfigureAwait ( false ) ;
6780 }
68- else
69- {
70- Task supressErrorTask = task . ContinueWith (
71- continuationAction : continuation ,
72- state : null ,
73- cancellationToken : CancellationToken . None ,
74- continuationOptions : s_tco ,
75- scheduler : TaskScheduler . Default ) ;
76- throw new TimeoutException ( ) ;
77- }
7881 }
7982#endif
8083 }
8184
82- public static async ValueTask TimeoutAfter ( this ValueTask task , TimeSpan timeout )
85+ public static async ValueTask TimeoutAfter ( this ValueTask valueTask , TimeSpan timeout )
8386 {
84- if ( task . IsCompletedSuccessfully )
87+ if ( valueTask . IsCompletedSuccessfully )
8588 {
8689 return ;
8790 }
8891
8992#if NET6_0_OR_GREATER
90- Task actualTask = task . AsTask ( ) ;
91- await actualTask . WaitAsync ( timeout )
93+ Task task = valueTask . AsTask ( ) ;
94+ await task . WaitAsync ( timeout )
9295 . ConfigureAwait ( false ) ;
9396#else
94- await DoTimeoutAfter ( task , timeout )
97+ await DoTimeoutAfter ( valueTask , timeout )
9598 . ConfigureAwait ( false ) ;
9699
97- async static ValueTask DoTimeoutAfter ( ValueTask task , TimeSpan timeout )
100+ // https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#using-a-timeout
101+ static async ValueTask DoTimeoutAfter ( ValueTask valueTask , TimeSpan timeout )
98102 {
99- Task actualTask = task . AsTask ( ) ;
100- if ( actualTask == await Task . WhenAny ( actualTask , Task . Delay ( timeout ) ) . ConfigureAwait ( false ) )
103+ Task task = valueTask . AsTask ( ) ;
104+ using ( var cts = new CancellationTokenSource ( ) )
101105 {
102- await actualTask . ConfigureAwait ( false ) ;
103- }
104- else
105- {
106- Task supressErrorTask = actualTask . ContinueWith (
107- continuationAction : continuation ,
108- state : null ,
109- cancellationToken : CancellationToken . None ,
110- continuationOptions : s_tco ,
111- scheduler : TaskScheduler . Default ) ;
112- throw new TimeoutException ( ) ;
106+ Task delayTask = Task . Delay ( timeout , cts . Token ) ;
107+ Task resultTask = await Task . WhenAny ( task , delayTask ) . ConfigureAwait ( false ) ;
108+ if ( resultTask == delayTask )
109+ {
110+ task . Ignore ( ) ;
111+ throw new TimeoutException ( ) ;
112+ }
113+ else
114+ {
115+ cts . Cancel ( ) ;
116+ }
117+
118+ await valueTask . ConfigureAwait ( false ) ;
113119 }
114120 }
115121#endif
116122 }
123+
124+ /*
125+ * https://devblogs.microsoft.com/dotnet/configureawait-faq/
126+ * I'm using GetAwaiter().GetResult(). Do I need to use ConfigureAwait(false)?
127+ * Answer: No
128+ */
129+ public static void EnsureCompleted ( this Task task )
130+ {
131+ task . GetAwaiter ( ) . GetResult ( ) ;
132+ }
133+
134+ public static T EnsureCompleted < T > ( this Task < T > task )
135+ {
136+ return task . GetAwaiter ( ) . GetResult ( ) ;
137+ }
138+
139+ public static T EnsureCompleted < T > ( this ValueTask < T > task )
140+ {
141+ return task . GetAwaiter ( ) . GetResult ( ) ;
142+ }
143+
144+ public static void EnsureCompleted ( this ValueTask task )
145+ {
146+ task . GetAwaiter ( ) . GetResult ( ) ;
147+ }
148+
149+ #if ! NET6_0_OR_GREATER
150+ // https://github.com/dotnet/runtime/issues/23878
151+ // https://github.com/dotnet/runtime/issues/23878#issuecomment-1398958645
152+ public static void Ignore ( this Task task )
153+ {
154+ if ( task . IsCompleted )
155+ {
156+ _ = task . Exception ;
157+ }
158+ else
159+ {
160+ _ = task . ContinueWith (
161+ continuationAction : IgnoreTaskContinuation ,
162+ state : null ,
163+ cancellationToken : CancellationToken . None ,
164+ continuationOptions : s_tco ,
165+ scheduler : TaskScheduler . Default ) ;
166+ }
167+ }
168+ #endif
117169 }
118170}
0 commit comments