6

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.

OschtärEi
  • 2,255
  • 3
  • 20
  • 41
  • Don't use `Task.Factory.StartNew`, use `Task.Run`. As for calling *SharePoint*, the context has the `ExecuteQueryAsync` method which can be adapted to return a Task – Panagiotis Kanavos Mar 02 '17 at 12:58
  • I don't have `ExecuteQueryAsync` in my CSOM library I'm using.... the other thing I changed. – OschtärEi Mar 02 '17 at 12:59
  • just upadted via nuget to `Microsoft.SharePointOnline.CSOM, 16.1.6216` - still no `ExecuteQueryAsync` there. – OschtärEi Mar 02 '17 at 13:05
  • 1
    The CSOM had an asynchronous [ExecuteQueryAsync](https://msdn.microsoft.com/en-us/library/jj168848(v=office.14).aspx) method since 2010. Which SharePoint version are you using, 2010, 2013 or Online? There are different CSOM packages for each one – Panagiotis Kanavos Mar 02 '17 at 13:07
  • Using `Microsoft.SharePointOnline.CSOM` on the newest version. One would assume this should be the newest, most up to date CSOM...but I cannot find this async method you mentioned there. – OschtärEi Mar 02 '17 at 13:11
  • 1
    The method mentioned by the *docs* you mean - although I see that the referenced dlls are for Silverlight only. Looks like they added the method *only there* but the doc page is incomplete. I had forgotten how ... intuitive the API and docs are. Anyway, the CSOM is just a set of REST calls in the end. If you care about scalability, you can always use HttpClient or OData. – Panagiotis Kanavos Mar 02 '17 at 13:29
  • 1
    PS - now you'll understand why some people call themselves SharePoint refugees – Panagiotis Kanavos Mar 02 '17 at 13:32

2 Answers2

5

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
0

This is my personal view of your problem and for me the above way is not required. When we host your API in IIS, the server assigns one thread from thread pool it has in the server. The IIS also has a setting of maxConcurrentRequestsPerCPU maxConcurrentThreadsPerCPU. You can setup these values to serve the request instead of handling the request all by yourself.

arun thatham
  • 500
  • 1
  • 4
  • 13