Last active
August 12, 2021 15:42
-
-
Save Shazwazza/c127e8c567ab505d146e54ac802a9945 to your computer and use it in GitHub Desktop.
Flowing ExecutionContext
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// These tests show the various ways to spawn child threads | |
// and in what scenarios the value in the AsyncLocal or | |
// logical CallContext will flow to the child threads. | |
// All of the below test results are identical if you | |
// use the old CallContext.LogicalSetData CallContext.LogicalGetData | |
void Main() | |
{ | |
// Set the AsyncLocal value and we'll see where this flows | |
var local = new AsyncLocal<string>(); | |
local.Value = "Test"; | |
var resetEvents = new List<ManualResetEventSlim>(); | |
ManualResetEventSlim createResetEvent() | |
{ | |
var e = new ManualResetEventSlim(false); | |
resetEvents.Add(e); | |
return e; | |
} | |
void logOutput(string name) | |
{ | |
Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}, {name} = {(local.Value ?? "NULL")}"); | |
} | |
// Will the AsyncLocal value flow to child threads? | |
// The below tests various ways to create and run | |
// child threads and what happends when | |
// ExecutionContext.SuppressFlow is used. | |
ExecutionContext.SuppressFlow(); | |
var t1 = Task.Run(() => | |
{ | |
// DOES NOT FLOW | |
logOutput("t1"); | |
}); | |
ExecutionContext.RestoreFlow(); | |
var t1a = Task.Run(() => | |
{ | |
ExecutionContext.SuppressFlow(); | |
// FLOWS! | |
// SuppressFlow must be called on the parent thread and | |
// IsFlowSuppressed is only a releveant flag value for the parent thread. | |
logOutput("t1a"); | |
ExecutionContext.RestoreFlow(); | |
}); | |
var t1b = t1.ContinueWith(x => { | |
// FLOWS! | |
// even though the parent task did not flow and it's the same thread | |
logOutput("t1b"); | |
}); | |
var t2 = Task.Run(() => | |
{ | |
// FLOWS | |
logOutput("t2"); | |
}); | |
var t2a = t2.ContinueWith(x => | |
{ | |
// FLOWS | |
logOutput("t2a"); | |
}); | |
ExecutionContext.SuppressFlow(); | |
var t2b = t2a.ContinueWith(x => | |
{ | |
// DOES NOT FLOW | |
logOutput("t2b"); | |
}); | |
ExecutionContext.RestoreFlow(); | |
var t3 = Task.Run(() => | |
{ | |
// FLOWS | |
logOutput("t3"); | |
}).ConfigureAwait(false); // Irrelevant to ExecutionContext | |
var r1 = createResetEvent(); | |
ThreadPool.UnsafeQueueUserWorkItem(arg => | |
{ | |
// DOES NOT FLOW | |
logOutput("t4"); | |
r1.Set(); | |
}, null); | |
var r2 = createResetEvent(); | |
ThreadPool.QueueUserWorkItem(arg => | |
{ | |
// FLOWS | |
logOutput("t5"); | |
r2.Set(); | |
}, null); | |
var t6 = Task.Factory.StartNew(() => | |
{ | |
// FLOWS | |
logOutput("t6"); | |
}, TaskCreationOptions.LongRunning); | |
var r3 = createResetEvent(); | |
var t7 = new Thread(() => { | |
// FLOWS | |
logOutput("t7"); | |
r3.Set(); | |
}); | |
t7.Start(); | |
var r3a = createResetEvent(); | |
var t7a = new Thread(() => | |
{ | |
// FLOWS | |
logOutput("t7a"); | |
r3a.Set(); | |
}) | |
{ | |
IsBackground = true // does not effect ExecutionContext | |
}; | |
t7a.Start(); | |
ExecutionContext.SuppressFlow(); | |
var r4 = createResetEvent(); | |
var t8 = new Thread(() => | |
{ | |
// DOES NOT FLOW | |
logOutput("t8"); | |
r4.Set(); | |
}); | |
t8.Start(); | |
ExecutionContext.RestoreFlow(); | |
ExecutionContext.SuppressFlow(); | |
var r5 = createResetEvent(); | |
var t8a = new Thread(() => | |
{ | |
// FLOWS | |
// RestoreFlow called before the thread started | |
logOutput("t8a"); | |
r5.Set(); | |
}); | |
ExecutionContext.RestoreFlow(); | |
t8a.Start(); | |
var r6 = createResetEvent(); | |
var t8b = new Thread(() => | |
{ | |
// DOES NOT FLOW | |
// SuppressFlow/RestoreFlow only matters when | |
// the thread is actually being started | |
logOutput("t8b"); | |
r6.Set(); | |
}); | |
ExecutionContext.SuppressFlow(); | |
t8b.Start(); | |
ExecutionContext.RestoreFlow(); | |
// Join all threads | |
Task.WaitAll(t1, t2, t6); | |
t3.GetAwaiter().GetResult(); | |
resetEvents.ForEach(x => x.Wait()); | |
// RESULT: | |
//Thread: 11, t1 = NULL | |
//Thread: 17, t1a = Test | |
//Thread: 11, t1b = Test | |
//Thread: 15, t2 = Test | |
//Thread: 15, t2a = Test | |
//Thread: 15, t2b = NULL | |
//Thread: 24, t3 = Test | |
//Thread: 23, t4 = NULL | |
//Thread: 25, t5 = Test | |
//Thread: 8, t6 = Test | |
//Thread: 16, t7 = Test | |
//Thread: 17, t7a = Test | |
//Thread: 31, t8 = NULL | |
//Thread: 32, t8a = Test | |
//Thread: 33, t8b = NULL | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment