-1

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?

Zer0
  • 7,191
  • 1
  • 20
  • 34
  • The only case for an improper use of `ConfigureAwait(false)` I can imagine of is when you need to update something on the UI, otherwise I though `ConfigureAwait(false)` was a good practice to avoid changing threads unnecessarily, also consider that console apps & Net Core (and maybe some other) doesn´t have `SynchronizationContext`. – Diego Osornio Feb 27 '19 at 16:30
  • @Unnamed I'm not using .NET Core (updated the tags). There are many times I don't want to use `ConfigureAwait(false)` such as in ASP.NET, WinForms, WPF threads and when I need the current culture for localization. Among other reasons. – Zer0 Feb 27 '19 at 17:01

2 Answers2

1

Create a SynchronizationContext that throws an exception in the implementation of Post and Send. Alternatively, have it set a boolean indicating whether Send or Post was called, allowing you to check that boolean later (if you do this you'll probably want to run the provided delegate, else you could risk a deadlock).

Set an instance of that custom synchronization context as the current synchronization context at the start of your test whenever testing a method that should never use the current synchronization context.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • +1 This works for unit tests and this helps. Any way I could do a compile time (or even run time throwing an exception) check without using a custom `SynchronizationContext`? – Zer0 Feb 27 '19 at 17:03
  • @Zer0 Any compile time check is only going to be a guess. You'll never be able to *know* if anything could ever use the current synchronization context because there isn't enough information available to determine that from the perspective of a code analysis tools, and the flow diagnostics you'd need to do are provably unsolvable problems in the general case, so you *will* have some number of false positives and/or false negatives. Writing a custom sync context this simple is so quick and easy I don't see why you'd want to try avoiding it. Avoiding it would be way more work, if it's possible. – Servy Feb 27 '19 at 17:14
  • Yeah I doubted compile time is possible, but maybe runtime? What I'm getting at is if there's a way to check without unit tests. I do agree this solution is simple and fits the bill, presuming applicable unit tests are in place. If something isn't unit tested I was wondering if I could "fail fast" within the method itself. – Zer0 Feb 27 '19 at 17:33
  • @Zer0 Sure. You don't need to do this in a unit test. You can set the sync context outside of a unit test. Presumably in that case you'd just set it to the default context, rather than one that throws. If you want to do that, you don't even need to avoid capturing the context in the method. If you do that don't forget to set the original context back when returning control to the caller. – Servy Feb 27 '19 at 18:20
1

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810