19

How do you avoid writing the same code twice for an async and a non async method. I am currently using ASP.NET so I am currently on the request thread, and I quickly learned that he below code (that should show my intent), is definetely the wrong way of doing this.

The application deadlocks, as the await keyword tries to get back on the same thread that the .Result is blocking.

The whole reason I am doing this, is to avoid writing the same "FindAll" code twice.

public IEnumerable<Resource> FindAll()
{
   return FindAllAsync().Result;

}

public async Task<IEnumerable<Resource>> FindAllAsync()
{
   return await Context.Resources.ToListAsync();
}

So how do you solve this?

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
André Snede
  • 9,899
  • 7
  • 43
  • 67
  • Arguably, you dont need both synchronous and asynchronous unless you want to support cancellation. Given the deferred iteration model of `IEnumerable`, cancellation of any iteration is provided for free. – Gusdor May 29 '14 at 12:17
  • Arguably I could make another example, of another object being returned, and the problem is still there. This question is not about IEnumerable but calling async methods from a synchronous context. – André Snede May 29 '14 at 12:19
  • Why do you need both methods? (Also, consider just not using async database queries at all. You can mix sync and async in the same app at no disadvantage.) – usr May 29 '14 at 12:35
  • @Gusdor. This is my question, I'm not saying you aren't right, but my questions isn't about IEnumerable, but a challenge I'm facing with async/await. The return type won't change the fact that the above code will cause a deadlock, no matter the return type. – André Snede May 29 '14 at 12:36
  • @usr Please provide an answer with an example of what you are suggesting. – André Snede May 29 '14 at 12:38

3 Answers3

16

How do you avoid writing the same code twice for an async and a non async method.

You can't, in the general case.

The operation in question is either naturally asynchronous or naturally synchronous. In this example (a database request), it is naturally asynchronous. So, make the API asynchronous. That is all.

Stephen Toub has a famous pair of blog posts Should I expose synchronous wrappers for asynchronous methods? and Should I expose asynchronous wrappers for synchronous methods? The short answer to both questions is "no."

You can do various hacks to expose both types of APIs (and Stephen covers each approach in his posts), but the benefits are minuscule compared to the drawbacks.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thanks Stephen, that really does end the discussion, and explains it very well. – André Snede May 31 '14 at 13:07
  • And if you keep it DRY and don't write a synchronous version of a naturally asynchronous operation, e.g. database code, but you also need to use it from synchronous code- say a console app- I guess you use `.GetAwaiter().GetResult()`. – jnm2 Nov 27 '15 at 13:56
  • 1
    Updated links: Should I expose synchronous wrappers for asynchronous methods?https://devblogs.microsoft.com/pfxteam/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ Should I expose asynchronous wrappers for synchronous methods?https://devblogs.microsoft.com/pfxteam/should-i-expose-asynchronous-wrappers-for-synchronous-methods/ – Miguel Marques Dec 15 '21 at 23:43
  • @MiguelMarques: Thanks! I updated the answer with the newer links you provided. – Stephen Cleary Dec 16 '21 at 15:12
3

Synchronous and Asynchronous methods should act differently. Usually that means that synchronous calls should call an API that blocks as a part of the request and async should call the API that is "async all the way"

If you don't want to create a completely synchronous api, in your case, you can use ConfigureAwait(false).

When you mark a Task with ConfigureAwait(false), what you're actually saying is "There's no need to run the continuation (the code after the await) inside the same SynchronizationContext, you may complete inside your current context (Which is usually a ThreadPool thread)"

As for your second method, you can remove the async keyword and save a redundant generation of a state machine:

public IEnumerable<Resource> FindAll()
{
     return FindAllAsync().Result;
}

public Task<IEnumerable<Resource>> FindAllAsync()
{
     return Context.Resources.ToListAsync();
}

Some reading references:

Stephan Cleary - Dont block on async code

Best practice to call ConfigureAwait(false)

Task.ConfigureAwait(false) MSDN

Community
  • 1
  • 1
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Won't this hurt the benifit of async when I actually want to use the async/await from top to bottom? – André Snede May 29 '14 at 12:21
  • Would you say it hurts? You should mark a method as `async` when you must await and do more work later. If there isn't a need to await you can simply return the `Task`. Now, whoever calls `FindAllAsync` may still `await` the returned `Task` – Yuval Itzchakov May 29 '14 at 12:23
  • I just don't exactly understand what ConfigureAwait does, and if it has a down side? To me it seems that it is a bad idea to call it when on a webrequest thread. – André Snede May 29 '14 at 12:26
  • @YuvalItzchakov ConfigureAwait doesn't mark the method, it "marks" the task. – i3arnon May 29 '14 at 12:38
  • 2
    This avoids the deadlock but it does not avoid the performance penalty introduced by using async IO and then waiting. The FindAll method gets the worst of both styles. Still, this is a valid way of reusing code. – usr May 29 '14 at 12:40
  • I definitely agree. The OP wanted a way to do it, that is why I started the post with my opinion of calling async code synchronously – Yuval Itzchakov May 29 '14 at 12:43
  • 1
    @YuvalItzchakov Great answer Yuval and thanks :) I do understand this isn't the most performant code, but I wanted to know how to combat this challenge. – André Snede May 29 '14 at 12:45
  • Just a note that your current code for `public Task> FindAllAsync()` won't compile. `ConfigureAwait` doesn't return a `Task`. – noseratio May 29 '14 at 19:32
  • You should [never use `.Result` and `.Wait()`](https://stackoverflow.com/questions/17284517/is-task-result-the-same-as-getawaiter-getresult) when calling async code from synchronous code. [Always use `.GetAwaiter().GetResult()`](http://blog.stephencleary.com/2014/12/a-tour-of-task-part-6-results.html). – jnm2 Nov 27 '15 at 14:02
-1

In my case (performance-sensitive library with both cases for "pure" sync and "pure" async methods to exist), I've recently found a code generator library that generates sync code at build-time, using async code as a basis : https://github.com/zompinc/sync-method-generator.

It works like a charm and allows me to finally get rid of these chunks of duplicate code. Only big change I had to do is make unit tests call async methods instead of sync methods (no performance at stake here).

Paul W
  • 149
  • 1
  • 6