public static void FlowTest()
{
ThreadTestHelpers.RunTestInBackgroundThread(() =>
{
var asyncLocal = new AsyncLocal<int>();
asyncLocal.Value = 1;
var asyncFlowControl = default(AsyncFlowControl);
Action<Action, Action> verifySuppressRestore =
(suppressFlow, restoreFlow) =>
{
VerifyExecutionContextFlow(asyncLocal, 1);
ExecutionContext executionContext2 = ExecutionContext.Capture();
suppressFlow();
VerifyExecutionContextFlow(asyncLocal, 0);
VerifyExecutionContext(executionContext2, asyncLocal, 1);
executionContext2 = ExecutionContext.Capture();
restoreFlow();
VerifyExecutionContextFlow(asyncLocal, 1);
VerifyExecutionContext(executionContext2, asyncLocal, 0);
};
verifySuppressRestore(
() => asyncFlowControl = ExecutionContext.SuppressFlow(),
() => asyncFlowControl.Undo());
verifySuppressRestore(
() => asyncFlowControl = ExecutionContext.SuppressFlow(),
() => asyncFlowControl.Dispose());
verifySuppressRestore(
() => ExecutionContext.SuppressFlow(),
() => ExecutionContext.RestoreFlow());
Assert.Throws<InvalidOperationException>(() => ExecutionContext.RestoreFlow());
asyncFlowControl = ExecutionContext.SuppressFlow();
Assert.Throws<InvalidOperationException>(() => ExecutionContext.SuppressFlow());
ThreadTestHelpers.RunTestInBackgroundThread(() =>
{
ExecutionContext.SuppressFlow();
Assert.Throws<InvalidOperationException>(() => asyncFlowControl.Undo());
Assert.Throws<InvalidOperationException>(() => asyncFlowControl.Dispose());
ExecutionContext.RestoreFlow();
});
asyncFlowControl.Undo();
Assert.Throws<InvalidOperationException>(() => asyncFlowControl.Undo());
Assert.Throws<InvalidOperationException>(() => asyncFlowControl.Dispose());
// Changing an async local value does not prevent undoing a flow-suppressed execution context. In .NET Core, the
// execution context is immutable, so changing an async local value changes the execution context instance,
// contrary to the desktop framework.
asyncFlowControl = ExecutionContext.SuppressFlow();
asyncLocal.Value = 2;
asyncFlowControl.Undo();
VerifyExecutionContextFlow(asyncLocal, 2);
asyncFlowControl = ExecutionContext.SuppressFlow();
asyncLocal.Value = 3;
asyncFlowControl.Dispose();
VerifyExecutionContextFlow(asyncLocal, 3);
ExecutionContext.SuppressFlow();
asyncLocal.Value = 4;
ExecutionContext.RestoreFlow();
VerifyExecutionContextFlow(asyncLocal, 4);
// An async flow control cannot be undone when a different execution context is applied. The desktop framework
// mutates the execution context when its state changes, and only changes the instance when an execution context
// is applied (for instance, through ExecutionContext.Run). The framework prevents a suppressed-flow execution
// context from being applied by returning null from ExecutionContext.Capture, so the only type of execution
// context that can be applied is one whose flow is not suppressed. After suppressing flow and changing an async
// local's value, the desktop framework verifies that a different execution context has not been applied by
// checking the execution context instance against the one saved from when flow was suppressed. In .NET Core,
// since the execution context instance will change after changing the async local's value, it verifies that a
// different execution context has not been applied, by instead ensuring that the current execution context's
// flow is suppressed.
{
ExecutionContext executionContext = null;
Action verifyCannotUndoAsyncFlowControlAfterChangingExecutionContext =
() =>
{
ExecutionContext.Run(
executionContext,
state =>
{
Assert.Throws<InvalidOperationException>(() => asyncFlowControl.Undo());
Assert.Throws<InvalidOperationException>(() => asyncFlowControl.Dispose());
},
null);
};
executionContext = ExecutionContext.Capture();
asyncFlowControl = ExecutionContext.SuppressFlow();
verifyCannotUndoAsyncFlowControlAfterChangingExecutionContext();
asyncFlowControl.Undo();
executionContext = ExecutionContext.Capture();
asyncFlowControl = ExecutionContext.SuppressFlow();
asyncLocal.Value = 5;
verifyCannotUndoAsyncFlowControlAfterChangingExecutionContext();
asyncFlowControl.Undo();
VerifyExecutionContextFlow(asyncLocal, 5);
}
});
}