-
Notifications
You must be signed in to change notification settings - Fork 10.3k
/
Copy pathTaskExtensions.cs
148 lines (132 loc) · 5.9 KB
/
TaskExtensions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// 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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
#if AspNetCoreTesting
namespace Microsoft.AspNetCore.InternalTesting;
#else
namespace System.Threading.Tasks.Extensions;
#endif
#if AspNetCoreTesting
public
#else
internal
#endif
static class TaskExtensions
{
#if DEBUG
// Shorter duration when running tests with debug.
// Less time waiting for hang unit tests to fail in aspnetcore solution.
public const int DefaultTimeoutDuration = 5 * 1000;
#else
public const int DefaultTimeoutDuration = 30 * 1000;
#endif
public static TimeSpan DefaultTimeoutTimeSpan { get; } = TimeSpan.FromMilliseconds(DefaultTimeoutDuration);
public static Task DefaultTimeout(this Task task, int milliseconds = DefaultTimeoutDuration, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default)
{
return task.TimeoutAfter(TimeSpan.FromMilliseconds(milliseconds), filePath, lineNumber);
}
public static Task DefaultTimeout(this Task task, TimeSpan timeout, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default)
{
return task.TimeoutAfter(timeout, filePath, lineNumber);
}
public static Task DefaultTimeout(this ValueTask task, int milliseconds = DefaultTimeoutDuration, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default)
{
return task.AsTask().TimeoutAfter(TimeSpan.FromMilliseconds(milliseconds), filePath, lineNumber);
}
public static Task DefaultTimeout(this ValueTask task, TimeSpan timeout, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default)
{
return task.AsTask().TimeoutAfter(timeout, filePath, lineNumber);
}
public static Task<T> DefaultTimeout<T>(this Task<T> task, int milliseconds = DefaultTimeoutDuration, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default)
{
return task.TimeoutAfter(TimeSpan.FromMilliseconds(milliseconds), filePath, lineNumber);
}
public static Task<T> DefaultTimeout<T>(this Task<T> task, TimeSpan timeout, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default)
{
return task.TimeoutAfter(timeout, filePath, lineNumber);
}
public static Task<T> DefaultTimeout<T>(this ValueTask<T> task, int milliseconds = DefaultTimeoutDuration, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default)
{
return task.AsTask().TimeoutAfter(TimeSpan.FromMilliseconds(milliseconds), filePath, lineNumber);
}
public static Task<T> DefaultTimeout<T>(this ValueTask<T> task, TimeSpan timeout, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default)
{
return task.AsTask().TimeoutAfter(timeout, filePath, lineNumber);
}
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
public static async Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout,
[CallerFilePath] string filePath = null,
[CallerLineNumber] int lineNumber = default)
{
// Don't create a timer if the task is already completed
// or the debugger is attached
if (task.IsCompleted || Debugger.IsAttached)
{
return await task.ConfigureAwait(false);
}
#if NET6_0_OR_GREATER
try
{
return await task.WaitAsync(timeout).ConfigureAwait(false);
}
catch (TimeoutException ex) when (ex.Source == typeof(TaskExtensions).Namespace)
{
throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber));
}
#else
var cts = new CancellationTokenSource();
if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token)).ConfigureAwait(false))
{
cts.Cancel();
return await task.ConfigureAwait(false);
}
else
{
throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber));
}
#endif
}
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
public static async Task TimeoutAfter(this Task task, TimeSpan timeout,
[CallerFilePath] string filePath = null,
[CallerLineNumber] int lineNumber = default)
{
// Don't create a timer if the task is already completed
// or the debugger is attached
if (task.IsCompleted || Debugger.IsAttached)
{
await task.ConfigureAwait(false);
return;
}
#if NET6_0_OR_GREATER
try
{
await task.WaitAsync(timeout).ConfigureAwait(false);
}
catch (TimeoutException ex) when (ex.Source == typeof(TaskExtensions).Namespace)
{
throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber));
}
#else
var cts = new CancellationTokenSource();
if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token)).ConfigureAwait(false))
{
cts.Cancel();
await task.ConfigureAwait(false);
}
else
{
throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber));
}
#endif
}
private static string CreateMessage(TimeSpan timeout, string filePath, int lineNumber)
=> string.IsNullOrEmpty(filePath)
? $"The operation timed out after reaching the limit of {timeout.TotalMilliseconds}ms."
: $"The operation at {filePath}:{lineNumber} timed out after reaching the limit of {timeout.TotalMilliseconds}ms.";
}