4

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.

9
  • In SomeClass.SynchronousAPIFunc() you can call differentInstanceOfClassB.FuncB().Result(); which will result in method FuncB being called synchronously Commented Jun 9, 2020 at 13:30
  • This can be done, but it will be easy to introduce subtle deadlocks. It sounds like you have a bad requirement, if not an absurd one Commented Jun 9, 2020 at 13:31
  • In this scenario it is usually better to transition to queue based or distributed workflow paradigm. If your API can run a function for several minutes, usually the calling framework will timeout or will interpret this as unresponsive. If the process genuinely takes that long, then change over to a pub-sub style architecture. Commented Jun 9, 2020 at 13:31
  • It's best to expose separate asynchronous / synchronous methods from your class Commented Jun 9, 2020 at 13:36
  • @AluanHaddad Using .GetAwaiter().GetResult() has the same risk of deadlock, so it's not any more "safe". All it does is unwrap the exception. Commented Jun 9, 2020 at 15:07

3 Answers 3

7

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the reply. I agree that this would be the tidiest approach. It might take quite a bit of restructuring though as these functions are very large and a lot of objects are involved. The chain of function calls goes through quite a lot of code. But it might be worth the refactoring, though it isn't always easy to convince others of the same.
I will flag this as the solution/answer, as it is the solution that has the least "code smell".
5

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.

4 Comments

Thanks! This seems very interesting in conjunction with the anwer I flagged as the solution (providing both sync and async functions). Though I do have a question now that I read this and the article you linked. Most examples I see of async function chaining use async in the signatures in all the functions in the chain (and await in all bodies), while you only use it for the innermost call. Will execution still return to the caller(s) if the async keyword is omitted further up the chain? Is it enough to just return the task at that point?
Sorry for not being clear. You will need to use this pattern all the way through the chain. Each method that can be sync or async would need to use this pattern.
Thanks for the reply! Sorry, I thin my wording was a bit poor. I was just wondering why the first line in your example doesn't say "public async Task<T> FuncBAsync() => await FuncBAsync(sync: false);". It isn't immediately clear to me why we dont use the async and await keywords on that line. Also I'm going to check out your book on the matter.
Yes, I do tend to elide async/await when it's just a simple passthrough.
2

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

3 Comments

Blocking code should definitely not be concealed in an API method.
This may deadlock in certain situations, it's a dangeruous aproach. The normal way would be to simply write two implementations if you really need them both.
Thanks for the reply! This was the method I had initially gone for, but I had already read the article you linked, and I started getting cold feet.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.