C# Async when also needing support for synchronous call

I'm in a situation where we have some code that is run by user input (button click), that runs through a series of function calls and result in generating some data (which is a quite heavy operation, several minutes). We'd like to use Async for this so that it doesn't lock up the UI while we're doing this operation.

But at the same time we also have a requirement that the functions will also be available through an API which preferably should be synchronous.

Visualization/Example (pseudo-code):

public async void Button_Click() // from UI context
{
  await instanceOfClassA.FuncA();

  // some code
}

public async Task ClassA.FuncA()
{
  await instanceOfClassB.FuncB()

  // some code
}

public async Task ClassB.FuncB()
{
  await instanceOfClassC.SomeHeavyFunc()

  // some code
}

public async Task ClassC.SomeHeavyFunc()
{
  // some heavy calculations
}


// Also need to provide a public synchronous API function
public void SomeClass.SynchronousAPIFunc()
{
  // need to call differentInstanceOfClassB.FuncB()
}

Is there a way to make it so that the public API function does the waiting for the async operation internally?

EDIT: In this post, user Rachel provides two answers to the question. Both seem interesting, though I'm unsure which one would offer the least amount of risk/side effects.

EDIT2: I should note that we're using .NET v4.6.1.

Thanks in advance.

Upvotes: 4

Views: 445

Answers (3)

Stephen Cleary
Stephen Cleary

Reputation: 457472

But at the same time we also have a requirement that the functions will also be available through an API which preferably should be synchronous.

If you have the need to expose both a synchronous and asynchronous API, I recommend the boolean argument hack. This looks like:

public Task<T> FuncBAsync() => FuncBAsync(sync: false);
public T FuncB() => FuncBAsync(sync: true).GetAwaiter().GetResult();
public async Task<T> FuncBAsync(bool sync)
{
  // Note: is `sync` is `true`, this method ***must*** return a completed task.
  ...
}

Is there a way to make it so that the public API function does the waiting for the async operation internally?

I do not recommend using direct blocking (e.g., GetAwaiter().GetResult()), as the straightforward implementation will lead to deadlocks.

EDIT: In this post, user Rachel provides two answers to the question.

I strongly recommend against using that solution. It uses a nested message loop with a custom SynchronizationContext, but doesn't do COM pumping. This can cause problems particularly if called from a UI thread. Even if the pumping isn't a problem, this solution can cause unexpected re-entrancy, which is a source of countless, extremely subtle, and difficult-to-find bugs.

Upvotes: 4

Gabriel Luci
Gabriel Luci

Reputation: 41018

The problem with making "synchronous" versions of your methods that just call the asynchronous versions is that it can cause deadlocks, especially if the person calling this code is not aware that this is what is happening.

If you really want to make synchronous versions, then follow Microsoft's lead and write completely new methods that do not use any asynchronous code. For example, the implementation for File.ReadAllLines() doesn't use any of the same code as File.ReadAllLinesAsync().

If you don't want to do that, then just don't provide synchronous versions of your methods. Let the caller make the decision on how to deal with it. If they want to block synchronously on it, then they can mitigate the risk of deadlock.

Upvotes: 7

Shaurya Gupta
Shaurya Gupta

Reputation: 197

You can utilize .GetAwaiter().GetResult()

as per your example, it would look like:

public void SomeClass.SynchronousAPIFunc()
{
  // need to call differentInstanceOfClassB.FuncB()
  ClassB.FuncB().GetAwaiter().GetResult();
}

Also, a good reference on when to not use the above can be found at Dont Block on Async Code

Upvotes: 2

Related Questions