Skip to content

Instantly share code, notes, and snippets.

@Shazwazza
Last active August 12, 2021 15:42
Show Gist options
  • Save Shazwazza/c127e8c567ab505d146e54ac802a9945 to your computer and use it in GitHub Desktop.
Save Shazwazza/c127e8c567ab505d146e54ac802a9945 to your computer and use it in GitHub Desktop.
Flowing ExecutionContext
// 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