forked from microsoft/vs-threading
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgressWithCompletion`1.cs
More file actions
235 lines (214 loc) · 10.4 KB
/
Copy pathProgressWithCompletion`1.cs
File metadata and controls
235 lines (214 loc) · 10.4 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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.VisualStudio.Threading
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// An incremental progress reporting mechanism that also allows
/// asynchronous awaiting for all reports to be processed.
/// </summary>
/// <typeparam name="T">The type of message sent in progress updates.</typeparam>
public class ProgressWithCompletion<T> : IProgress<T>
{
/// <summary>
/// The synchronization object.
/// Applicable only when <see cref="joinableTaskFactory"/> is null.
/// </summary>
private readonly object? syncObject;
/// <summary>
/// The handler to invoke for each progress update.
/// </summary>
private readonly Func<T, Task> handler;
/// <summary>
/// The set of progress reports that have started (but may not have finished yet).
/// Applicable only when <see cref="joinableTaskFactory"/> is null.
/// </summary>
private readonly HashSet<Task>? outstandingTasks;
/// <summary>
/// The factory to use for invoking the <see cref="handler"/>.
/// Applicable only when <see cref="joinableTaskFactory"/> is null.
/// </summary>
private readonly TaskFactory? taskFactory;
/// <summary>
/// A value indicating whether this instance was constructed on the main thread.
/// Applicable only when <see cref="joinableTaskFactory"/> is not null.
/// </summary>
private readonly bool createdOnMainThread;
/// <summary>
/// The <see cref="JoinableTaskFactory"/> to use when invoking the <see cref="handler"/> to mitigate deadlocks.
/// May be null.
/// </summary>
private readonly JoinableTaskFactory? joinableTaskFactory;
/// <summary>
/// A collection of outstanding progress updates that have not completed execution.
/// Applicable only when <see cref="joinableTaskFactory"/> is not null.
/// </summary>
private readonly JoinableTaskCollection? outstandingJoinableTasks;
/// <summary>
/// Initializes a new instance of the <see cref="ProgressWithCompletion{T}" /> class.
/// </summary>
/// <param name="handler">
/// A handler to invoke for each reported progress value.
/// Depending on the <see cref="SynchronizationContext"/> instance that is captured when this constructor is invoked,
/// it is possible that this handler instance could be invoked concurrently with itself.
/// </param>
public ProgressWithCompletion(Action<T> handler)
: this(WrapSyncHandler(handler), joinableTaskFactory: null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ProgressWithCompletion{T}" /> class.
/// </summary>
/// <param name="handler">
/// A handler to invoke for each reported progress value.
/// Depending on the <see cref="SynchronizationContext"/> instance that is captured when this constructor is invoked,
/// it is possible that this handler instance could be invoked concurrently with itself.
/// </param>
public ProgressWithCompletion(Func<T, Task> handler)
: this(handler, joinableTaskFactory: null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ProgressWithCompletion{T}" /> class.
/// </summary>
/// <param name="handler">
/// A handler to invoke for each reported progress value.
/// It is possible that this handler instance could be invoked concurrently with itself.
/// </param>
/// <param name="joinableTaskFactory">A <see cref="JoinableTaskFactory"/> instance that can be used to mitigate deadlocks when <see cref="WaitAsync(CancellationToken)"/> is called and the <paramref name="handler"/> requires the main thread.</param>
public ProgressWithCompletion(Action<T> handler, JoinableTaskFactory? joinableTaskFactory)
: this(WrapSyncHandler(handler), joinableTaskFactory)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ProgressWithCompletion{T}" /> class.
/// </summary>
/// <param name="handler">
/// A handler to invoke for each reported progress value.
/// It is possible that this handler instance could be invoked concurrently with itself.
/// </param>
/// <param name="joinableTaskFactory">A <see cref="JoinableTaskFactory"/> instance that can be used to mitigate deadlocks when <see cref="WaitAsync(CancellationToken)"/> is called and the <paramref name="handler"/> requires the main thread.</param>
public ProgressWithCompletion(Func<T, Task> handler, JoinableTaskFactory? joinableTaskFactory)
{
Requires.NotNull(handler, nameof(handler));
this.handler = handler;
if (joinableTaskFactory is object)
{
this.joinableTaskFactory = joinableTaskFactory;
this.outstandingJoinableTasks = joinableTaskFactory.Context.CreateCollection();
this.createdOnMainThread = joinableTaskFactory.Context.IsOnMainThread;
}
else
{
this.syncObject = new object();
this.taskFactory = new TaskFactory(SynchronizationContext.Current is object ? TaskScheduler.FromCurrentSynchronizationContext() : TaskScheduler.Default);
this.outstandingTasks = new HashSet<Task>();
}
}
/// <summary>
/// Receives a progress update.
/// </summary>
/// <param name="value">The value representing the updated progress.</param>
void IProgress<T>.Report(T value)
{
this.Report(value);
}
/// <summary>
/// Returns a task that completes when all reported progress has executed.
/// </summary>
/// <returns>A task that completes when all progress is complete.</returns>
public Task WaitAsync() => this.WaitAsync(CancellationToken.None);
/// <summary>
/// Returns a task that completes when all reported progress has executed.
/// </summary>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>A task that completes when all progress is complete.</returns>
public Task WaitAsync(CancellationToken cancellationToken)
{
if (this.IsJoinableTaskAware(out _, out JoinableTaskCollection? outstandingJoinableTasks, out var syncObject, out HashSet<Task>? outstandingTasks, out _))
{
return outstandingJoinableTasks.JoinTillEmptyAsync(cancellationToken);
}
else
{
lock (syncObject)
{
return Task.WhenAll(outstandingTasks).WithCancellation(cancellationToken);
}
}
}
/// <summary>
/// Receives a progress update.
/// </summary>
/// <param name="value">The value representing the updated progress.</param>
protected virtual void Report(T value)
{
if (this.IsJoinableTaskAware(out JoinableTaskFactory? joinableTaskFactory, out JoinableTaskCollection? outstandingJoinableTasks, out var syncObject, out HashSet<Task>? outstandingTasks, out TaskFactory? taskFactory))
{
JoinableTask joinableTask = joinableTaskFactory.RunAsync(
async delegate
{
// Emulate the behavior of having captured a SynchronizationContext by invoking the handler on the main thread
// if the constructor was on the main thread. Otherwise use the threadpool thread. But never invoke the handler
// inline with our caller, per the behavior folks expect from this and .NET's Progress<T> class.
if (this.createdOnMainThread)
{
await joinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true);
}
else
{
await TaskScheduler.Default.SwitchTo(alwaysYield: true);
}
await this.handler(value).ConfigureAwaitRunInline();
});
outstandingJoinableTasks.Add(joinableTask);
}
else
{
Task? reported = taskFactory.StartNew(() => this.handler(value)).Unwrap();
lock (syncObject)
{
outstandingTasks.Add(reported);
}
reported.ContinueWith(
t =>
{
lock (syncObject)
{
outstandingTasks.Remove(t);
}
},
CancellationToken.None,
TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
}
private static Func<T, Task> WrapSyncHandler(Action<T> handler)
{
Requires.NotNull(handler, nameof(handler));
return value =>
{
handler(value);
return Task.CompletedTask;
};
}
private bool IsJoinableTaskAware(
[NotNullWhen(true)] out JoinableTaskFactory? joinableTaskFactory,
[NotNullWhen(true)] out JoinableTaskCollection? outstandingJoinableTasks,
[NotNullWhen(false)] out object? syncObject,
[NotNullWhen(false)] out HashSet<Task>? outstandingTasks,
[NotNullWhen(false)] out TaskFactory? taskFactory)
{
joinableTaskFactory = this.joinableTaskFactory;
outstandingJoinableTasks = this.outstandingJoinableTasks;
syncObject = this.syncObject;
outstandingTasks = this.outstandingTasks;
taskFactory = this.taskFactory;
return joinableTaskFactory is object;
}
}
}