Write a Well Designed Async/Non-Async API

Write a well designed async / non-async API

If you want the most maintainable option, only provide an async API, which is implemented without making any blocking calls or using any thread pool threads.

If you really want to have both async and synchronous APIs, then you'll encounter a maintainability problem. You really need to implement it twice: once async and once synchronous. Both of those methods will look nearly identical so the initial implementation is easy, but you will end up with two separate nearly-identical methods so maintenance is problematic.

In particular, there's a no good and simple way to just make an async or synchronous "wrapper". Stephen Toub has the best info on the subject:

  1. Should I expose asynchronous wrappers for synchronous methods?
  2. Should I expose synchronous wrappers for asynchronous methods?

(the short answer to both questions is "no")

However, there are some hacks you can use if you want to avoid the duplicated implementation; the best one is usually the boolean argument hack.

Add async in a non-async interfaces

You cannot change your interface to (optionally) support asynchronous calls in a way that maintains backward compatibility—and that’s a good thing.

Explanation

Remember, the entire purpose of an interface is to guarantee that all callers can interact with a concrete implementation of that interface without knowing anything about the concrete type itself. Implementing a method as async changes that method’s signature—i.e., by returning a Task<> and potentially expecting an await keyword, depending on how it’s called—and, therefore, breaks the interface. You can change the signature, but by definition that breaks backward compatibility with the existing interface.

Options

Given this, there are three textbook solutions to this problem:

  1. Handle the asynchronous calls within your existing synchronous interface, as suggested in @Neil's answer. This maintains backwards compatibility, but eliminates the benefits of asynchronous calls.
  2. Change the interface to use async, thus requiring all implementations to be updated. This is the most invasive approach, and may not be practical if you don't own all implementations.
  3. Create a new async version of your interface (e.g., IServiceAsync) which is used by implementations and callers requiring asynchronous functionality, as suggested in @Ryan Wilson’s comment.

If your priority is exclusively backward compatibility with existing code, and not the performance benefits of asynchronous processing, you should take the first option.

Assuming you want the performance benefits of asynchronous calls, however, the last option makes the most sense given that you are working with legacy code; that will be especially true if you don’t own all of the implementations.

The rest of this answer will assume the third option.

Base Interface

If appropriate, your synchronous and asynchronous interfaces can derive from a common interface which shares any non-async methods, thus allowing them to be used interchangeably in those scenarios. This is useful if you have code that doesn’t rely on any of the implicated async methods. Your question doesn't suggest that, but I'm assuming your interface may be more involved than what's included here.

Central Manager

Your central manager will need to be updated to look for both IService and IServiceAsync, and conditionally call e.g., RunAsync() on the latter. Just make sure you’re truly taking advantage of the asynchronous capabilities in that case (e.g., by adding these to a task queue and processing tasks as they complete). Otherwise, you won't gain any performance benefits from the asynchronous interface.

Impact

I recognize that your objective was to avoid updating your central manager. Unfortunately, though, there’s no way to accomplish this while also taking advantage of any asynchronous processing desired for the API call.

How can I implement both an async method and its synchronous counterpart?

If your library needs to implement both synchronous and asynchronous members, then you implement both members. There are no shortcuts (assuming this is intended to be a reusable library).

public async Task<string> GetContentAsync(string url)
{
... // Logic here, e.g., using HttpClient
}

public string GetContent(string url)
{
... // Duplicate logic here, e.g., using WebClient
}

The duplication of logic is certainly unfortunate, but if you try to take shortcuts you'll actually end up in a worse situation. The details of "why" are a bit long for an SO answer, but Stephen Toub covers the problems that arise when wrapping in his classic pair of blog posts "Should I expose synchronous wrappers for asynchronous methods?" and "Should I expose asynchronous wrappers for synchronous methods?"

BTW, the answer to both questions is "no". Also, see my SO answer here.

Async library method usage in non-async methods

If you're using Web API I would not go for an async Log method if the method is not truly async. Have a look at this answer from Stephen Cleary.

Why would i use async instead of non async method


what is the difference?

Scalability. Specifically, synchronous methods block the calling thread, and asynchronous methods do not.

What this means (for an ASP.NET application) is that synchronous action methods block the request thread for the duration of the request, and that asynchronous action methods do not block that thread.

This, in turn, yields greater scalability. Since the thread pool is a limited resource (and in particular, since it has a limited thread injection rate), asynchronous code allows your app to handle greater load with fewer threads.

For more information, see the "Synchronous vs. Asynchronous Request Handling" section in my article on async ASP.NET; that section of the article applies to all server technologies, including ASP.NET Core. You can also check out this gist which demonstrates the scalability difference by limiting the thread pool growth rate. I haven't ported that example to ASP.NET Core, but it should be straightforward to do.

Making a sequence of async API calls to an external provider

Given each async Task is awaited, your code will run sequentially. However, based on the code it seems you could execute the upload tasks concurrently (see example below).

Example showing how you could execute the uploads concurrently:

var client = await clientRepository.FirstOrDefaultAsync(c => c.UserId == userId);
if (client == null)
throw new ApiException("Failed to read client");

// Send data to provider
var applicant = await apiClient.CreateApplicantAsync(client);
if (applicant == null || applicant.Id.IsNullOrEmpty())
throw new ApiException("Failed to create applicant");

// Send files a to provider
var docUploadA = apiClient.UploadDocumentAsync(applicant.Id, dto.DocumentA.Stream, dto.DocumentA.FileName, dto.DocumentA.Document.Type);
var docUploadB = apiClient.UploadDocumentAsync(applicant.Id, dto.DocumentB.Stream, dto.DocumentB.FileName, dto.DocumentB.Document.Type);
var imageUpload = apiClient.UploadApplicantImageAsync(applicant.Id, dto.DocumentC.Stream, dto.DocumentC.FileName);
await Task.WhenAll(docUploadA, docUploadB, imageUpload);
if (docUploadA.Result == null || docUploadB.Result == null || imageUpload.Result == null)
throw new ApiException("Failed to upload document");

// Check the applicant
var checkResponse = await apiClient.CheckApplicantAsync(applicant.Id);
if (checkResponse == null)
throw new ApiException("Applicant check failed");

// Check is successful when the Result property is null
if (checkResponse.Result.IsNullOrEmpty())
{
result.Success();
}

How Do I Call an Async Method from a Non-Async Method?


Am I missing something?

Yes. Asynchronous code - by its nature - implies that the current thread is not used while the operation is in progress. Synchronous code - by its nature - implies that the current thread is blocked while the operation is in progress. This is why calling asynchronous code from synchronous code literally doesn't even make sense. In fact, as I describe on my blog, a naive approach (using Result/Wait) can easily result in deadlocks.

The first thing to consider is: should my API be synchronous or asynchronous? If it deals with I/O (as in this example), it should be asynchronous. So, this would be a more appropriate design:

public async Task<string> RetrieveHolidayDatesFromSourceAsync() {
var result = await this.DoRetrieveHolidayDatesFromSourceAsync();
/** Do stuff **/
var returnedResult = this.TransformResults(result); /** Where result gets used **/
return returnedResult;
}

As I describe in my async best practices article, you should go "async all the way". If you don't, you won't get any benefit out of async anyway, so why bother?

But let's say that you're interested in eventually going async, but right now you can't change everything, you just want to change part of your app. That's a pretty common situation.

In that case, the proper approach is to expose both synchronous and asynchronous APIs. Eventually, after all the other code is upgraded, the synchronous APIs can be removed. I explore a variety of options for this kind of scenario in my article on brownfield async development; my personal favorite is the "bool parameter hack", which would look like this:

public string RetrieveHolidayDatesFromSource() {
return this.DoRetrieveHolidayDatesFromSourceAsync(sync: true).GetAwaiter().GetResult();
}

public Task<string> RetrieveHolidayDatesFromSourceAsync() {
return this.DoRetrieveHolidayDatesFromSourceAsync(sync: false);
}

private async Task<string> DoRetrieveHolidayDatesFromSourceAsync(bool sync) {
var result = await this.GetHolidayDatesAsync(sync);
/** Do stuff **/
var returnedResult = this.TransformResults(result);
return returnedResult;
}

private async Task<string> GetHolidayDatesAsync(bool sync) {
using (var client = new WebClient()) {
return sync
? client.DownloadString(SourceURI)
: await client.DownloadStringTaskAsync(SourceURI);
}
}

This approach avoids code duplication and also avoids any deadlock or reentrancy problems common with other "sync-over-async" antipattern solutions.

Note that I would still treat the resulting code as an "intermediate step" on the path to a properly-asynchronous API. In particular, the inner code had to fall back on WebClient (which supports both sync and async) instead of the preferred HttpClient (which only supports async). Once all the calling code is changed to use RetrieveHolidayDatesFromSourceAsync and not RetrieveHolidayDatesFromSource, then I'd revisit this and remove all the tech debt, changing it to use HttpClient and be async-only.

How to implement One method that support both sync / async calls?

Marking the method as async just means that the method will have an await somewhere in the implementation. It is not mandatory to return a Task if the method is async, it is only mandatory to have at least an await.
You should only return Task if you want to make the method awaitable.
I personally have a method like:

public async void Button_Click(object sender, EventArgs e);

So, answering to your question, yes it is true what you've made, the method will only be async when it gets to :

await crud.UpdateAsync(crud.DataBuffer);

otherwise it is sync even though it has async keyword.
I just dont get it why you have a sync call when you can have an async



Related Topics



Leave a reply



Submit