We can write asynchronous code with Python by using a library called Asyncio, though. Python has another library called Aiohttp that is an HTTP client and server based on Asyncio. Thus, we can use Asyncio to create asynchronous API calls. This is useful for optimizing code.
Yes, it is a best practice to make your API Endpoints asynchronous in according to this Microsoft's article. An asynchronous operations allows to "optimize" amount of threads involved in request handling by involving the same thread to process another request, while the previous is under await (more details here).
It is a programming technique that allows us to execute our flows without blocking our application or causing the thread pool starvation. The often misconception is that by using the async and await keywords we gain better performance in terms of the speed of our application.
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:
(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.
I agree with both Marc and Stephen (Cleary).
(BTW, I started to write this as a comment to Stephen's answer, but it turned out to be too long; let me know if it is OK to write this as an answer or not, and feel free to take bits from it and add it to Stephen's answer, in the spirit of "providing the one best answer").
It really "depends": like Marc said, it is important to know how DoSomethingAsync is asynchronous. We all agree that there is no point in having a the "sync" method call the "async" method and "wait": this can be done in user code. The only advantage of having a separate method is to have actual performance gains, to have an implementation which is, under the hood, different and tailored to the synchronous scenario. This is especially true if the "async" method is creating a thread (or taking it from a threadpool): you end up with something that underneath uses two "control flows", while "promising" with its synchronous looks to be executed in the callers' context. This may even have concurrency issues, depending on the implementation.
Also in other cases, like the intensive I/O that the OP is mentioning, it may be worth having two different implementation. Most operating systems (Windows for sure) have for I/O different mechanisms tailored to the two scenarios: for example, async execution of and I/O operation takes great advantages from OS level mechanisms like I/O completion ports, which add a little overhead (not significant, but not null) in the kernel (after all, they have to do bookkeeping, dispatch, etc.), and more direct implementation for synchronous operations. Code complexity also varies a lot, especially in functions where multiple operations are done/coordinated.
What I would do is:
This mainly because two goals are somehow in contrast with one another. If you want maintainable code, the obvious choice is implementing sync in terms of async/wait (or the other way around) (or, even better, provide only the async variant and let the user do "wait"); if you want performance you should implement the two functions differently, to exploit different underlying mechanisms (from the framework or from the OS). I think that it should not make difference from a unit-testing point of view how you actually implement your API.
I ran into the same problem but managed to find a compromise between efficiency and maintainability using two simple facts about async methods:
This is better to be shown on example:
//Simple synchronous methods that starts third party component, waits for a second and gets result.
public ThirdPartyResult Execute(ThirdPartyOptions options)
{
ThirdPartyComponent.Start(options);
System.Threading.Thread.Sleep(1000);
return ThirdPartyComponent.GetResult();
}
To provide maintainable sync/async version of this method it has been split to three layers:
//Lower level - parts that work differently for sync/async version.
//When isAsync is false there are no await operators and method is running synchronously.
private static async Task Wait(bool isAsync, int milliseconds)
{
if (isAsync)
{
await Task.Delay(milliseconds);
}
else
{
System.Threading.Thread.Sleep(milliseconds);
}
}
//Middle level - the main algorithm.
//When isAsync is false the only awaited method is running synchronously,
//so the whole algorithm is running synchronously.
private async Task<ThirdPartyResult> Execute(bool isAsync, ThirdPartyOptions options)
{
ThirdPartyComponent.Start(options);
await Wait(isAsync, 1000);
return ThirdPartyComponent.GetResult();
}
//Upper level - public synchronous API.
//Internal method runs synchronously and will be already finished when Result property is accessed.
public ThirdPartyResult ExecuteSync(ThirdPartyOptions options)
{
return Execute(false, options).Result;
}
//Upper level - public asynchronous API.
public async Task<ThirdPartyResult> ExecuteAsync(ThirdPartyOptions options)
{
return await Execute(true, options);
}
The main advantage here is that middle level algorithm which is most likely to change is implemented only once so developer don't have to maintain two almost identical pieces of code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With