Skip to content

Commit 94bec76

Browse files
authored
Allow default AsyncFlowControls rather than throwing (#82912)
ExecutionContext.SuppressFlow currently throws an exception if flow is already suppressed. This makes it complicated to use, as you need to check whether IsFlowSuppressed first and take two different paths based on the result. If we instead just allow SuppressFlow to return a default AsyncFlowControl rather than throwing, and have AsyncFlowControl's Undo nop rather than throw if it doesn't contain a Thread, we can again make it simple to just always use SuppressFlow without any of the other complications.
1 parent db084d9 commit 94bec76

File tree

12 files changed

+67
-194
lines changed

12 files changed

+67
-194
lines changed

src/libraries/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.MultipleWatchers.cs

+4-9
Original file line numberDiff line numberDiff line change
@@ -79,20 +79,15 @@ public void FileSystemWatcher_File_Create_SuppressedExecutionContextHandled()
7979

8080
local.Value = 42;
8181

82-
ExecutionContext.SuppressFlow();
83-
try
82+
using (ExecutionContext.SuppressFlow())
8483
{
8584
watcher1.EnableRaisingEvents = true;
8685
}
87-
finally
88-
{
89-
ExecutionContext.RestoreFlow();
90-
}
9186

92-
File.Create(fileName).Dispose();
93-
tcs1.Task.Wait(WaitForExpectedEventTimeout);
87+
File.Create(fileName).Dispose();
88+
tcs1.Task.Wait(WaitForExpectedEventTimeout);
9489

95-
Assert.Equal(0, tcs1.Task.Result);
90+
Assert.Equal(0, tcs1.Task.Result);
9691
}
9792
}
9893

src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs

+1-12
Original file line numberDiff line numberDiff line change
@@ -1291,15 +1291,8 @@ internal void HandleAltSvc(IEnumerable<string> altSvcHeaderValues, TimeSpan? res
12911291
{
12921292
var thisRef = new WeakReference<HttpConnectionPool>(this);
12931293

1294-
bool restoreFlow = false;
1295-
try
1294+
using (ExecutionContext.SuppressFlow())
12961295
{
1297-
if (!ExecutionContext.IsFlowSuppressed())
1298-
{
1299-
ExecutionContext.SuppressFlow();
1300-
restoreFlow = true;
1301-
}
1302-
13031296
_authorityExpireTimer = new Timer(static o =>
13041297
{
13051298
var wr = (WeakReference<HttpConnectionPool>)o!;
@@ -1309,10 +1302,6 @@ internal void HandleAltSvc(IEnumerable<string> altSvcHeaderValues, TimeSpan? res
13091302
}
13101303
}, thisRef, nextAuthorityMaxAge, Timeout.InfiniteTimeSpan);
13111304
}
1312-
finally
1313-
{
1314-
if (restoreFlow) ExecutionContext.RestoreFlow();
1315-
}
13161305
}
13171306
else
13181307
{

src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPoolManager.cs

+2-25
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,8 @@ public HttpConnectionPoolManager(HttpConnectionSettings settings)
9191
_cleanPoolTimeout = timerPeriod.TotalSeconds >= MinScavengeSeconds ? timerPeriod : TimeSpan.FromSeconds(MinScavengeSeconds);
9292
}
9393

94-
bool restoreFlow = false;
95-
try
94+
using (ExecutionContext.SuppressFlow()) // Don't capture the current ExecutionContext and its AsyncLocals onto the timer causing them to live forever
9695
{
97-
// Don't capture the current ExecutionContext and its AsyncLocals onto the timer causing them to live forever
98-
if (!ExecutionContext.IsFlowSuppressed())
99-
{
100-
ExecutionContext.SuppressFlow();
101-
restoreFlow = true;
102-
}
103-
10496
// Create the timer. Ensure the Timer has a weak reference to this manager; otherwise, it
10597
// can introduce a cycle that keeps the HttpConnectionPoolManager rooted by the Timer
10698
// implementation until the handler is Disposed (or indefinitely if it's not).
@@ -131,14 +123,6 @@ public HttpConnectionPoolManager(HttpConnectionSettings settings)
131123
}, thisRef, heartBeatInterval, heartBeatInterval);
132124
}
133125
}
134-
finally
135-
{
136-
// Restore the current ExecutionContext
137-
if (restoreFlow)
138-
{
139-
ExecutionContext.RestoreFlow();
140-
}
141-
}
142126
}
143127

144128
// Figure out proxy stuff.
@@ -190,14 +174,7 @@ public void StartMonitoringNetworkChanges()
190174
return;
191175
}
192176

193-
if (!ExecutionContext.IsFlowSuppressed())
194-
{
195-
using (ExecutionContext.SuppressFlow())
196-
{
197-
NetworkChange.NetworkAddressChanged += networkChangedDelegate;
198-
}
199-
}
200-
else
177+
using (ExecutionContext.SuppressFlow())
201178
{
202179
NetworkChange.NetworkAddressChanged += networkChangedDelegate;
203180
}

src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Unix.cs

+1-14
Original file line numberDiff line numberDiff line change
@@ -104,23 +104,10 @@ public static event NetworkAvailabilityChangedEventHandler? NetworkAvailabilityC
104104
if (s_availabilityTimer == null)
105105
{
106106
// Don't capture the current ExecutionContext and its AsyncLocals onto the timer causing them to live forever
107-
bool restoreFlow = false;
108-
try
107+
using (ExecutionContext.SuppressFlow())
109108
{
110-
if (!ExecutionContext.IsFlowSuppressed())
111-
{
112-
ExecutionContext.SuppressFlow();
113-
restoreFlow = true;
114-
}
115-
116109
s_availabilityTimer = new Timer(s_availabilityTimerFiredCallback, null, Timeout.Infinite, Timeout.Infinite);
117110
}
118-
finally
119-
{
120-
// Restore the current ExecutionContext
121-
if (restoreFlow)
122-
ExecutionContext.RestoreFlow();
123-
}
124111
}
125112

126113
s_availabilityChangedSubscribers.TryAdd(value, ExecutionContext.Capture());

src/libraries/System.Net.Sockets/tests/FunctionalTests/ExecutionContextFlowTest.cs

+11-67
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,10 @@ public async Task SocketAsyncEventArgs_ExecutionContextFlowsAcrossAcceptAsyncOpe
3333
};
3434

3535
asyncLocal.Value = 42;
36-
if (suppressContext) ExecutionContext.SuppressFlow();
37-
try
36+
using (suppressContext ? ExecutionContext.SuppressFlow() : default)
3837
{
3938
Assert.True(listener.AcceptAsync(saea));
4039
}
41-
finally
42-
{
43-
if (suppressContext) ExecutionContext.RestoreFlow();
44-
}
4540
asyncLocal.Value = 0;
4641

4742
client.Connect(listener.LocalEndPoint);
@@ -65,19 +60,14 @@ public async Task APM_ExecutionContextFlowsAcrossBeginAcceptOperation(bool suppr
6560
var tcs = new TaskCompletionSource<int>();
6661

6762
asyncLocal.Value = 42;
68-
if (suppressContext) ExecutionContext.SuppressFlow();
69-
try
63+
using (suppressContext ? ExecutionContext.SuppressFlow() : default)
7064
{
7165
listener.BeginAccept(iar =>
7266
{
7367
listener.EndAccept(iar).Dispose();
7468
tcs.SetResult(asyncLocal.Value);
7569
}, null);
7670
}
77-
finally
78-
{
79-
if (suppressContext) ExecutionContext.RestoreFlow();
80-
}
8171
asyncLocal.Value = 0;
8272

8373
client.Connect(listener.LocalEndPoint);
@@ -105,15 +95,10 @@ public async Task SocketAsyncEventArgs_ExecutionContextFlowsAcrossConnectAsyncOp
10595

10696
bool pending;
10797
asyncLocal.Value = 42;
108-
if (suppressContext) ExecutionContext.SuppressFlow();
109-
try
98+
using (suppressContext ? ExecutionContext.SuppressFlow() : default)
11099
{
111100
pending = client.ConnectAsync(saea);
112101
}
113-
finally
114-
{
115-
if (suppressContext) ExecutionContext.RestoreFlow();
116-
}
117102
asyncLocal.Value = 0;
118103

119104
if (pending)
@@ -139,19 +124,14 @@ public async Task APM_ExecutionContextFlowsAcrossBeginConnectOperation(bool supp
139124

140125
bool pending;
141126
asyncLocal.Value = 42;
142-
if (suppressContext) ExecutionContext.SuppressFlow();
143-
try
127+
using (suppressContext ? ExecutionContext.SuppressFlow() : default)
144128
{
145129
pending = !client.BeginConnect(listener.LocalEndPoint, iar =>
146130
{
147131
client.EndConnect(iar);
148132
tcs.SetResult(asyncLocal.Value);
149133
}, null).CompletedSynchronously;
150134
}
151-
finally
152-
{
153-
if (suppressContext) ExecutionContext.RestoreFlow();
154-
}
155135
asyncLocal.Value = 0;
156136

157137
if (pending)
@@ -182,15 +162,10 @@ public async Task SocketAsyncEventArgs_ExecutionContextFlowsAcrossDisconnectAsyn
182162

183163
bool pending;
184164
asyncLocal.Value = 42;
185-
if (suppressContext) ExecutionContext.SuppressFlow();
186-
try
165+
using (suppressContext ? ExecutionContext.SuppressFlow() : default)
187166
{
188167
pending = client.DisconnectAsync(saea);
189168
}
190-
finally
191-
{
192-
if (suppressContext) ExecutionContext.RestoreFlow();
193-
}
194169
asyncLocal.Value = 0;
195170

196171
if (pending)
@@ -220,19 +195,14 @@ public async Task APM_ExecutionContextFlowsAcrossBeginDisconnectOperation(bool s
220195

221196
bool pending;
222197
asyncLocal.Value = 42;
223-
if (suppressContext) ExecutionContext.SuppressFlow();
224-
try
198+
using (suppressContext ? ExecutionContext.SuppressFlow() : default)
225199
{
226200
pending = !client.BeginDisconnect(reuseSocket: false, iar =>
227201
{
228202
client.EndDisconnect(iar);
229203
tcs.SetResult(asyncLocal.Value);
230204
}, null).CompletedSynchronously;
231205
}
232-
finally
233-
{
234-
if (suppressContext) ExecutionContext.RestoreFlow();
235-
}
236206
asyncLocal.Value = 0;
237207

238208
if (pending)
@@ -267,17 +237,12 @@ public async Task SocketAsyncEventArgs_ExecutionContextFlowsAcrossReceiveAsyncOp
267237
saea.RemoteEndPoint = server.LocalEndPoint;
268238

269239
asyncLocal.Value = 42;
270-
if (suppressContext) ExecutionContext.SuppressFlow();
271-
try
240+
using (suppressContext ? ExecutionContext.SuppressFlow() : default)
272241
{
273242
Assert.True(receiveFrom ?
274243
client.ReceiveFromAsync(saea) :
275244
client.ReceiveAsync(saea));
276245
}
277-
finally
278-
{
279-
if (suppressContext) ExecutionContext.RestoreFlow();
280-
}
281246
asyncLocal.Value = 0;
282247

283248
server.Send(new byte[] { 18 });
@@ -306,8 +271,7 @@ public async Task APM_ExecutionContextFlowsAcrossBeginReceiveOperation(bool supp
306271
var tcs = new TaskCompletionSource<int>();
307272

308273
asyncLocal.Value = 42;
309-
if (suppressContext) ExecutionContext.SuppressFlow();
310-
try
274+
using (suppressContext ? ExecutionContext.SuppressFlow() : default)
311275
{
312276
EndPoint ep = server.LocalEndPoint;
313277
Assert.False(receiveFrom ?
@@ -322,11 +286,6 @@ public async Task APM_ExecutionContextFlowsAcrossBeginReceiveOperation(bool supp
322286
tcs.SetResult(asyncLocal.Value);
323287
}, null).CompletedSynchronously);
324288
}
325-
finally
326-
{
327-
if (suppressContext)
328-
ExecutionContext.RestoreFlow();
329-
}
330289
asyncLocal.Value = 0;
331290

332291
server.Send(new byte[] { 18 });
@@ -365,18 +324,13 @@ public async Task SocketAsyncEventArgs_ExecutionContextFlowsAcrossSendAsyncOpera
365324

366325
bool pending;
367326
asyncLocal.Value = 42;
368-
if (suppressContext) ExecutionContext.SuppressFlow();
369-
try
327+
using (suppressContext ? ExecutionContext.SuppressFlow() : default)
370328
{
371329
pending =
372330
sendMode == 0 ? client.SendAsync(saea) :
373331
sendMode == 1 ? client.SendToAsync(saea) :
374332
client.SendPacketsAsync(saea);
375333
}
376-
finally
377-
{
378-
if (suppressContext) ExecutionContext.RestoreFlow();
379-
}
380334
asyncLocal.Value = 0;
381335

382336
int totalReceived = 0;
@@ -416,8 +370,7 @@ public async Task APM_ExecutionContextFlowsAcrossBeginSendOperation(bool suppres
416370

417371
bool pending;
418372
asyncLocal.Value = 42;
419-
if (suppressContext) ExecutionContext.SuppressFlow();
420-
try
373+
using (suppressContext ? ExecutionContext.SuppressFlow() : default)
421374
{
422375
pending = sendTo ?
423376
!client.BeginSendTo(buffer, 0, buffer.Length, SocketFlags.None, server.LocalEndPoint, iar =>
@@ -431,10 +384,6 @@ public async Task APM_ExecutionContextFlowsAcrossBeginSendOperation(bool suppres
431384
tcs.SetResult(asyncLocal.Value);
432385
}, null).CompletedSynchronously;
433386
}
434-
finally
435-
{
436-
if (suppressContext) ExecutionContext.RestoreFlow();
437-
}
438387
asyncLocal.Value = 0;
439388

440389
int totalReceived = 0;
@@ -477,19 +426,14 @@ public async Task APM_ExecutionContextFlowsAcrossBeginSendFileOperation(bool sup
477426

478427
bool pending;
479428
asyncLocal.Value = 42;
480-
if (suppressContext) ExecutionContext.SuppressFlow();
481-
try
429+
using (suppressContext ? ExecutionContext.SuppressFlow() : default)
482430
{
483431
pending = !client.BeginSendFile(filePath, iar =>
484432
{
485433
client.EndSendFile(iar);
486434
tcs.SetResult(asyncLocal.Value);
487435
}, null).CompletedSynchronously;
488436
}
489-
finally
490-
{
491-
if (suppressContext) ExecutionContext.RestoreFlow();
492-
}
493437
asyncLocal.Value = 0;
494438

495439
if (pending)

src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketAsyncEventArgsTest.cs

+17-19
Original file line numberDiff line numberDiff line change
@@ -122,27 +122,25 @@ await Task.WhenAll(
122122
using (Socket server = await acceptTask)
123123
using (var receiveSaea = new SocketAsyncEventArgs())
124124
{
125-
if (suppressed)
125+
using (suppressed ? ExecutionContext.SuppressFlow() : default)
126126
{
127-
ExecutionContext.SuppressFlow();
128-
}
129-
130-
var local = new AsyncLocal<int>();
131-
local.Value = 42;
132-
int threadId = Environment.CurrentManagedThreadId;
127+
var local = new AsyncLocal<int>();
128+
local.Value = 42;
129+
int threadId = Environment.CurrentManagedThreadId;
133130

134-
var mres = new ManualResetEventSlim();
135-
receiveSaea.SetBuffer(new byte[1], 0, 1);
136-
receiveSaea.Completed += delegate
137-
{
138-
Assert.NotEqual(threadId, Environment.CurrentManagedThreadId);
139-
Assert.Equal(suppressed ? 0 : 42, local.Value);
140-
mres.Set();
141-
};
142-
143-
Assert.True(client.ReceiveAsync(receiveSaea));
144-
server.Send(new byte[1]);
145-
mres.Wait();
131+
var mres = new ManualResetEventSlim();
132+
receiveSaea.SetBuffer(new byte[1], 0, 1);
133+
receiveSaea.Completed += delegate
134+
{
135+
Assert.NotEqual(threadId, Environment.CurrentManagedThreadId);
136+
Assert.Equal(suppressed ? 0 : 42, local.Value);
137+
mres.Set();
138+
};
139+
140+
Assert.True(client.ReceiveAsync(receiveSaea));
141+
server.Send(new byte[1]);
142+
mres.Wait();
143+
}
146144
}
147145
}
148146
}

0 commit comments

Comments
 (0)