I might be missing the answer somewhere, or it's something trivial, but I haven't found it.
Here's effectively what I'm trying to accomplish in code:
public static async Task CapturesContext()
{
await Task.Run(() => DoWhatever());
}
public static async Task DoesNotCaptureContext()
{
await Task.Run(() => DoWhatever()).ConfigureAwait(false);
}
public static void DoWhatever()
{
//Any way to test here that it was run with/without a captured SynchronizationContext?
}
The above is a very over-simplified example but expresses what I'm looking to accomplish.
The goal is to weed out improper usages of ConfigureAwait
in a very large code base.
Given any method(s) that are run using a Task
is it possible to check, via code like an Assert
or unit test, whether the given method is being run using a captured SynchronizationContext
?
If not, is there some alternate way I can accomplish my goal?
The goal is to weed out improper usages of ConfigureAwait in a very large code base.
Some teams choose to use a code analysis tool for this. There are several available. The most common approach I've seen is to require a ConfigureAwait
for every await
, and explicitly specify either true
or false
. This ensures that each await
has been reviewed and the flow of context is explicit. Other teams apply project-specific rules of "always use ConfigureAwait(false)
" and just depend on code review for projects that can't follow that rule.
The problem with your example code is that it's not possible for DoWhatever
to know whether it was indirectly invoked, because of the Task.Run
. If you rewrite those methods, this becomes clear:
public static async Task CapturesContext()
{
var task = Task.Run(() => DoWhatever());
await task;
}
public static async Task DoesNotCaptureContext()
{
var task = Task.Run(() => DoWhatever());
var configuredTask = task.ConfigureAwait(false);
await configuredTask;
}
The first lines of the rewritten methods should make it clear that DoWhatever
has no idea whether CapturesContext
or DoesNotCaptureContext
will capture the context or not. Note the "will" (future tense) - it is entirely possible that DoWhatever
runs and finishes executing before ConfigureAwait(false)
is even called.
Now, you can check from within a task whether it is running on a context right now. But in this case, for both example methods, DoWhatever
will not see a context due to the Task.Run
. So that doesn't help you detect the fact that CapturesContext
does capture the context; DoWhatever
doesn't see the context so it can't detect it.
The custom SynchronizationContext
is a good solution for unit tests, but it would be awkward to use at runtime, since you do have some methods that need the context. For this reason, most teams choose to depend on code review and/or code analysis tooling.