Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrapping a non async-method (which does web calls) into async

I know you should only use async for stuff which is not "CPU-intensive", e.g. file writes, web calls etc. and therefore I also know it doesn't makes sense to wrap every method into Task.Run or something similar.

However what should I do when I know a method does a web call, but it doesn't offer an async interface. Is it in this case worth to wrap it?

Concrete example:

I'm using CSOM (Client SharePoint Object Model) in my WebApi application (server) and want to get a SharePoint list.

This is normally done like this:

[HttpGet]
[Route("foo/{webUrl}")]
public int GetNumberOfLists(string webUrl)
{
    using (ClientContext context = new ClientContext(webUrl))
    {
        Web web = context.Web; 
        context.Load(web.Lists); 
        context.ExecuteQuery(); 

        return web.Lists.Count;
    }
}

And I thought about changing it to something like this:

[HttpGet]
[Route("foo/{webUrl}")]
public async Task<int> GetNumberOfLists(string webUrl)
{
    using (ClientContext context = new ClientContext(webUrl))
    {
        Web web = context.Web; 
        context.Load(web.Lists); 
        await Task.Run(() => clientContext.ExecuteQuery());

        return web.Lists.Count;
    }
}

Does it make sense and does it help? As I understand it, I just create / need a new thread for executing the query ("overhead") but at least the request thread will be free / ready for another request (that would be good).

But is it worth it and should it be done like this?

If so: Isn't it strange that Microsoft doesn't offer the "async" method out of the box or did they just not care about it?

edit: updated to use Task.Run as suggested in comment.

like image 310
OschtärEi Avatar asked Mar 02 '17 12:03

OschtärEi


1 Answers

However what should I do when I know a method does a web call, but it doesn't offer an async interface.

Unfortunately still somewhat common. As different libraries update their APIs, they will eventually catch up.

Is it in this case worth to wrap it?

Yes, if you're dealing with a UI thread. Otherwise, no.

Concrete example... in my WebApi application (server)

Then, no, you don't want to wrap in Task.Run. As noted in my article on async ASP.NET:

You can kick off some background work by awaiting Task.Run, but there’s no point in doing so. In fact, that will actually hurt your scalability by interfering with the ASP.NET thread pool heuristics... As a general rule, don’t queue work to the thread pool on ASP.NET.

Wrapping with Task.Run on ASP.NET:

  • Interferes with the ASP.NET thread pool heuristics twice (by taking a thread now and then releasing it later).
  • Adds overhead (code has to switch threads).
  • Does not free up a thread (the total number of threads used for this request is almost equal to just calling the synchronous version).

As I understand it, I just create / need a new thread for executing the query ("overhead") but at least the request thread will be free / ready for another request (that would be good).

Yes, but all you're doing is jumping threads, for no benefit. The thread used to block on the query result is one less thread ASP.NET has to use to handle requests, so freeing up one thread by consuming another isn't a good tradeoff.

Isn't it strange that Microsoft doesn't offer the "async" method out of the box or did they just not care about it?

Some of the "older" MS APIs just haven't gotten around to adding async versions yet. They certainly should, but developer time is a finite resource.

like image 106
Stephen Cleary Avatar answered Sep 20 '22 17:09

Stephen Cleary