简体   繁体   中英

Web API Sync Calls Best Practice

Probably this question has already been made, but I never found a definitive answer . Let's say that I have a Web API 2.0 Application hosted on IIS . I think I understand that best practice (to prevent deadlocks on client) is always use async methods from the GUI event to the HttpClient calls. And this is good and it works. But what is the best practice in case I had client application that does not have a GUI (eg Window Service, Console Application) but only synchronous methods from which to make the call? In this case, I use the following logic:

void MySyncMethodOnMyWindowServiceApp()
{
  list = GetDataAsync().Result().ToObject<List<MyClass>>();
} 

async Task<Jarray> GetDataAsync()
{
  list = await Client.GetAsync(<...>).ConfigureAwait(false);
  return await response.Content.ReadAsAsync<JArray>().ConfigureAwait(false);
}

But unfortunately this can still cause deadlocks on client that occur at random times on random machines.

The client app stops at this point and never returns:

list = await Client.GetAsync(<...>).ConfigureAwait(false);

If it's something that can be run in the background and isn't forced to be synchronous, try wrapping the code (that calls the async method) in a Task.Run(). I'm not sure that'll solve a "deadlock" problem (if it's something out of sync, that's another issue), but if you want to benefit from async/await, if you don't have async all the way down, I'm not sure there's a benefit unless you run it in a background thread. I had a case where adding Task.Run() in a few places (in my case, from an MVC controller which I changed to be async) and calling async methods not only improved performance slightly, but it improved reliability (not sure that it was a "deadlock" but seemed like something similar) under heavier load.

You will find that using Task.Run() is regarded by some as a bad way to do it, but I really couldn't see a better way to do it in my situation, and it really did seem to be an improvement. Perhaps this is one of those things where there's the ideal way to do it vs. the way to make it work in the imperfect situation that you're in. :-)

[Updated due to requests for code]

So, as someone else posted, you should do "async all the way down". In my case, my data wasn't async, but my UI was. So, I went async down as far as I could, then I wrapped my data calls with Task.Run in such as way that it made sense. That's the trick, I think, to figure out if it makes sense that things can run in parallel, otherwise, you're just being synchronous (if you use async and immediately resolve it, forcing it to wait for the answer). I had a number of reads that I could perform in parallel.

In the above example, I think you have to async up as far as makes sense, and then at some point, determine where you can spin off at hread and perform the operation independent of the other code. Let's say you have an operation that saves data, but you don't really need to wait for a response -- you're saving it and you're done. The only thing you might have to watch out for is not to close the program without waiting for that thread/task to finish. Where it makes sense in your code is up to you.

Syntax is pretty easy. I took existing code, changed the controller to an async returning a Task of my class that was formerly being returned.

var myTask = Task.Run(() =>
{
   //...some code that can run independently....  In my case, loading data
});
// ...other code that can run at the same time as the above....

await Task.WhenAll(myTask, otherTask); 
//..or...
await myTask;

//At this point, the result is available from the task
myDataValue = myTask.Result;

See MSDN for probably better examples: https://msdn.microsoft.com/en-us/library/hh195051(v=vs.110).aspx

[Update 2, more relevant for the original question]

Let's say that your data read is an async method.

private async Task<MyClass> Read()

You can call it, save the task, and await on it when ready:

var runTask = Read();
//... do other code that can run in parallel

await runTask;

So, for this purpose, calling async code, which is what the original poster is requesting, I don't think you need Task.Run(), although I don't think you can use "await" unless you're an async method -- you'll need an alternate syntax for Wait.

The trick is that without having some code to run in parallel, there's little point in it, so thinking about multi-threading is still the point.

Using Task<T>.Result is the equivalent of Wait which will perform a synchronous block on the thread. Having async methods on the WebApi and then having all the callers synchronously blocking them effectively makes the WebApi method synchronous. Under load you will deadlock if the number of simultaneous Waits exceeds the server/app thread pool.

So remember the rule of thumb " async all the way down ". You want the long running task (getting a collection of List) to be async. If the calling method must be sync you want to make that conversion from async to sync (using either Result or Wait) as close to the "ground" as possible. Keep they long running process async and have the sync portion as short as possible. That will greatly reduce the length of time that threads are blocked.

So for example you can do something like this.

void MySyncMethodOnMyWindowServiceApp()
{
   List<MyClass> myClasses = GetMyClassCollectionAsync().Result;
}

Task<List<MyClass>> GetMyListCollectionAsync()
{
   var data = await GetDataAsync(); // <- long running call to remote WebApi?
   return data.ToObject<List<MyClass>>();
}

The key part is the long running task remains async and not blocked because await is used.

Also don't confuse the responsiveness with scalability. Both are valid reasons for async. Yes responsiveness is a reason for using async (to avoid blocking on the UI thread). You are correct this wouldn't apply to a back end service however this isn't why async is used on a WebApi. The WebApi is also a non GUI back end process. If the only advantage of async code was responsiveness of the UI layer then WebApi would be sync code from start to finish. The other reason for using async is scalability (avoiding deadlocks) and this is the reason why WebApi calls are plumbed async. Keeping the long running processes async helps IIS make more efficient use of a limited number of threads. By default there are only 12 worker threads per core. This can be raised but that isn't a magic bullet either as threads are relatively expensive (about 1MB overhead per thread). await allows you to do more with less. More concurrent long running processes on less threads before a deadlock occurs.

The problem you are having with deadlocks must stem from something else. Your use of ConfigureAwait(false) prevents deadlocks here. Solve the bug and you are fine.

See Should we switch to use async I/O by default? to which the answer is "no". You should decide on a case by case basis and choose async when the benefits outweigh the costs. It is important to understand that async IO has a productivity cost associated with it. In non-GUI scenarios only a few targeted scenarios derive any benefit at all from async IO. The benefits can be enormous, though, but only in those cases.

Here's another helpful post: https://stackoverflow.com/a/25087273/122718

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM