forked from microsoft/vs-threading
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCancellationTokenExtensions.cs
More file actions
202 lines (175 loc) · 8.28 KB
/
Copy pathCancellationTokenExtensions.cs
File metadata and controls
202 lines (175 loc) · 8.28 KB
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Threading;
namespace Microsoft.VisualStudio.Threading;
/// <summary>
/// Extensions to <see cref="CancellationToken"/>.
/// </summary>
public static class CancellationTokenExtensions
{
/// <summary>
/// Creates a new <see cref="CancellationToken"/> that is canceled when any of a set of other tokens are canceled.
/// </summary>
/// <param name="original">The first token.</param>
/// <param name="other">The second token.</param>
/// <returns>A struct that contains the combined <see cref="CancellationToken"/> and a means to release memory when you're done using it.</returns>
public static CombinedCancellationToken CombineWith(this CancellationToken original, CancellationToken other)
{
if (original.IsCancellationRequested || !other.CanBeCanceled)
{
return new CombinedCancellationToken(original);
}
if (other.IsCancellationRequested || !original.CanBeCanceled)
{
return new CombinedCancellationToken(other);
}
// This is the most expensive path to take since it involves allocating memory and requiring disposal.
// Before this point we've checked every condition that would allow us to avoid it.
return new CombinedCancellationToken(CancellationTokenSource.CreateLinkedTokenSource(original, other));
}
/// <summary>
/// Creates a new <see cref="CancellationToken"/> that is canceled when any of a set of other tokens are canceled.
/// </summary>
/// <param name="original">The first token.</param>
/// <param name="others">The additional tokens.</param>
/// <returns>A struct that contains the combined <see cref="CancellationToken"/> and a means to release memory when you're done using it.</returns>
public static CombinedCancellationToken CombineWith(this CancellationToken original, params CancellationToken[] others)
{
Requires.NotNull(others, nameof(others));
if (original.IsCancellationRequested)
{
return new CombinedCancellationToken(original);
}
int cancelableTokensCount = original.CanBeCanceled ? 1 : 0;
foreach (CancellationToken other in others)
{
if (other.IsCancellationRequested)
{
return new CombinedCancellationToken(other);
}
if (other.CanBeCanceled)
{
cancelableTokensCount++;
}
}
switch (cancelableTokensCount)
{
case 0:
return new CombinedCancellationToken(CancellationToken.None);
case 1:
if (original.CanBeCanceled)
{
return new CombinedCancellationToken(original);
}
foreach (CancellationToken other in others)
{
if (other.CanBeCanceled)
{
return new CombinedCancellationToken(other);
}
}
throw Assumes.NotReachable();
case 2:
CancellationToken first = CancellationToken.None;
CancellationToken second = CancellationToken.None;
if (original.CanBeCanceled)
{
first = original;
}
foreach (CancellationToken other in others)
{
if (other.CanBeCanceled)
{
if (first.CanBeCanceled)
{
second = other;
}
else
{
first = other;
}
}
}
Assumes.True(first.CanBeCanceled && second.CanBeCanceled);
// Call the overload that takes two CancellationTokens explicitly to avoid an array allocation.
return new CombinedCancellationToken(CancellationTokenSource.CreateLinkedTokenSource(first, second));
default:
// This is the most expensive path to take since it involves allocating memory and requiring disposal.
// Before this point we've checked every condition that would allow us to avoid it.
var cancelableTokens = new CancellationToken[cancelableTokensCount];
int i = 0;
foreach (CancellationToken other in others)
{
if (other.CanBeCanceled)
{
cancelableTokens[i++] = other;
}
}
return new CombinedCancellationToken(CancellationTokenSource.CreateLinkedTokenSource(cancelableTokens));
}
}
/// <summary>
/// Provides access to a <see cref="System.Threading.CancellationToken"/> that combines multiple other tokens,
/// and allows convenient disposal of any applicable <see cref="CancellationTokenSource"/>.
/// </summary>
public readonly struct CombinedCancellationToken : IDisposable, IEquatable<CombinedCancellationToken>
{
/// <summary>
/// The object to dispose when this struct is disposed.
/// </summary>
private readonly CancellationTokenSource? cts;
/// <summary>
/// Initializes a new instance of the <see cref="CombinedCancellationToken"/> struct
/// that contains an aggregate <see cref="System.Threading.CancellationToken"/> whose source must be disposed.
/// </summary>
/// <param name="cancellationTokenSource">The cancellation token source.</param>
public CombinedCancellationToken(CancellationTokenSource cancellationTokenSource)
{
Requires.NotNull(cancellationTokenSource);
this.cts = cancellationTokenSource;
this.Token = cancellationTokenSource.Token;
}
/// <summary>
/// Initializes a new instance of the <see cref="CombinedCancellationToken"/> struct
/// that represents just a single, non-disposable <see cref="System.Threading.CancellationToken"/>.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
public CombinedCancellationToken(CancellationToken cancellationToken)
{
this.cts = null;
this.Token = cancellationToken;
}
/// <summary>
/// Gets the combined cancellation token.
/// </summary>
public CancellationToken Token { get; }
/// <summary>
/// Checks whether two instances of <see cref="CombinedCancellationToken"/> are equal.
/// </summary>
/// <param name="left">The left operand.</param>
/// <param name="right">The right operand.</param>
/// <returns><see langword="true" /> if they are equal; <see langword="false" /> otherwise.</returns>
public static bool operator ==(CombinedCancellationToken left, CombinedCancellationToken right) => left.Equals(right);
/// <summary>
/// Checks whether two instances of <see cref="CombinedCancellationToken"/> are not equal.
/// </summary>
/// <param name="left">The left operand.</param>
/// <param name="right">The right operand.</param>
/// <returns><see langword="true" /> if they are not equal; <see langword="false" /> if they are equal.</returns>
public static bool operator !=(CombinedCancellationToken left, CombinedCancellationToken right) => !(left == right);
/// <summary>
/// Disposes the <see cref="CancellationTokenSource"/> behind this combined token, if any.
/// </summary>
public void Dispose()
{
this.cts?.Dispose();
}
/// <inheritdoc />
public override bool Equals(object? obj) => obj is CombinedCancellationToken other && this.Equals(other);
/// <inheritdoc />
public bool Equals(CombinedCancellationToken other) => this.cts == other.cts && this.Token.Equals(other.Token);
/// <inheritdoc />
public override int GetHashCode() => (this.cts?.GetHashCode() ?? 0) + this.Token.GetHashCode();
}
}