@@ -39,7 +39,7 @@ internal static class TaskExtensions
39
39
{
40
40
#if ! NET6_0_OR_GREATER
41
41
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 ) ;
43
43
#endif
44
44
45
45
public static Task TimeoutAfter ( this Task task , TimeSpan timeout )
@@ -59,60 +59,112 @@ public static Task TimeoutAfter(this Task task, TimeSpan timeout)
59
59
60
60
return DoTimeoutAfter ( task , timeout ) ;
61
61
62
+ // https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#using-a-timeout
62
63
static async Task DoTimeoutAfter ( Task task , TimeSpan timeout )
63
64
{
64
- if ( task == await Task . WhenAny ( task , Task . Delay ( timeout ) ) . ConfigureAwait ( false ) )
65
+ using ( var cts = new CancellationTokenSource ( ) )
65
66
{
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
+
66
79
await task . ConfigureAwait ( false ) ;
67
80
}
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
- }
78
81
}
79
82
#endif
80
83
}
81
84
82
- public static async ValueTask TimeoutAfter ( this ValueTask task , TimeSpan timeout )
85
+ public static async ValueTask TimeoutAfter ( this ValueTask valueTask , TimeSpan timeout )
83
86
{
84
- if ( task . IsCompletedSuccessfully )
87
+ if ( valueTask . IsCompletedSuccessfully )
85
88
{
86
89
return ;
87
90
}
88
91
89
92
#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 )
92
95
. ConfigureAwait ( false ) ;
93
96
#else
94
- await DoTimeoutAfter ( task , timeout )
97
+ await DoTimeoutAfter ( valueTask , timeout )
95
98
. ConfigureAwait ( false ) ;
96
99
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 )
98
102
{
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 ( ) )
101
105
{
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 ) ;
113
119
}
114
120
}
115
121
#endif
116
122
}
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
117
169
}
118
170
}
0 commit comments