1

I'm using Microsoft's AsyncHelper (source) to call an async method from a synchronous context. This works just fine:

Result r = AsyncHelper.RunSync(async () => await SomeClass.SomeMethodAsync());

However, if I extract the Task and then try to run this extracted task synchronously instead, I encounter a deadlock.:

Task<Result> t = SomeClass.SomeMethodAsync();
Result r = AsyncHelper.RunSync(async () => await t);

Why does this happen, and what can be done to make this run?

TheHvidsten
  • 4,028
  • 3
  • 29
  • 62
  • Notice how `AsyncHelper.RunSync` is running `SomeClass.SomeMethodAsync` by calling `_myTaskFactory.StartNew`. This makes sure that there's no SynchronziationContext installed on the thread which starts executing `SomeClass.SomeMethodAsync`. Indeed, `AsyncHelper.RunSync` works *because* it calls `SomeMethodAsync` on a thread with no `SynchronizationContext` installed on it. The way to avoid this is to make sure that `SomeMethodAsync` is called on a thread without a `SynchronizationContext` on it, by using something like `AsyncHelper.RunAsync`... – canton7 Jan 07 '20 at 13:30
  • 1
    This looks a lot like "sync over async", in which case yes: this is a very well-known problem, which is exactly *why* you're not meant to do "sync over async". Ultimately, you can't go **slightly** async; you need to go async all the way. The only **supported** answer to the question, then, is to remove the sync-over-async: `Result r = await SomeClass.SomeMethodAsync();` (and make the code path you're working on `async` / `[ValueTask][]`-based) – Marc Gravell Jan 07 '20 at 13:45

1 Answers1

3

AsyncHelper.RunSync uses the thread pool hack to ensure that its delegate is called without a context, thus making it safe to block on (assuming that the delegate is safe to be called on a thread pool thread). In your code, SomeMethodAsync is executed on a thread pool thread, so any awaits will not capture a context.

what can be done to make this run?

Well, you would use the first code sample instead of the second.

If you want to have a construct that represents some code to run, then you should use a delegate type such as Func<Task<T>>. E.g.:

Func<Task<Result>> func = () => SomeClass.SomeMethodAsync();
Result r = AsyncHelper.RunSync(func);

With asynchronous code, Task<T> does not represent some code to run; it represents some code that has already started. Use Func<Task<T>> to represent some code to run.

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