6

I have the nuget package Esri.ArcGISRuntime and I need to call the method QueryTask.ExecuteAsync in one of my Web API 2 controllers. There is no synchronous counter part so in my library c# code I am using a wrapper like

    private QueryResult ExecuteSync()
    {
        var queryResults = ExecuteAsync();
        queryResults.Wait();
        return queryResults.Result;
    }

    private async Task<QueryResult> ExecuteQueryTaskAsync()
    {
        var queryTask = new QueryTask(_uri);
        return await queryTask.ExecuteAsync(_query).ConfigureAwait(false);
    }

Which works perfectly in my program/service. But using ExecuteSync this way in a Web API 2 controller causes it to completely freeze up and never return a response.

I have done some research and believe the culprit is mentioned here: http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

I absolutely do not want to use the function asynchronously. The above functions are so core and hidden deep within like 4 wrappers, that it would be a major overhaul of my library class to bubble up async methods just to support this one web api call.

I am looking for work-arounds/hacks/suggestions around this weird behavior of web API to allow me to run this async method synchronously and not have it deadlock

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
ParoX
  • 5,685
  • 23
  • 81
  • 152
  • You have to use `await`, read [this](http://stackoverflow.com/questions/13140523/await-vs-task-wait-deadlock) – Legends Feb 04 '17 at 02:34
  • @AlexeiLevenkov I assume you mean the "inner synchronization context" comment, in which I think it needs more content or become more of an official answer. The link doesnt isn't really geared at my web api controller example. Still not sure what to do as a work around – ParoX Feb 04 '17 at 03:05
  • Hmm you already have `ConfigureAwait(false)`... The code should not deadlock (so I've changed title and removed wrong duplicate) - it would be good idea to look where threads a stuck when deadlock happens. Also try very basic async method (`Task.Delay(1000)`) to confirm that problem is not related to library. – Alexei Levenkov Feb 04 '17 at 03:21

2 Answers2

15

I absolutely do not want to use the function asynchronously.

I have to say it, though. Asynchronous code is the best solution. The operation you're doing is asynchronous, and exposing a synchronous API for it is problematic at best.

Does it take time? Sure. But your code will be better off for it.

I am looking for work-arounds/hacks/suggestions

I have an entire article on the subject of brownfield async development, where I cover all the known hacks along with their drawbacks.

In your particular case (calling from WebApi on non-Core ASP.NET and considering that it does work from a Console/Win32Service-style app), I'd say the Thread Pool Hack should work for you. It looks like this:

private QueryResult ExecuteSync()
{
  return Task.Run(() => ExecuteAsync()).GetAwaiter().GetResult();
}

The idea is that ExecuteAsync is run on a thread pool thread, outside of the request context. The request thread is then blocked until the asynchronous work completes.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • The thread pool hack worked. It causes a weird issue with my customAttribute on my controller but after removing that attribute it worked. I would recommend this to anyone as a quickfix until you can find the time to async upwards (I have a deadline I have to meet, so this saves me). Thanks – ParoX Feb 04 '17 at 18:47
  • 1
    Couldnt this be done with ExecuteAsync().ConfigureAwait(false).GetAwaiter().GetResult()? – victor Aug 24 '17 at 15:15
  • 1
    @victor: No. I explain [why in my article](https://msdn.microsoft.com/en-us/magazine/mt238404.aspx). In short, the `ConfigureAwait(false)` does nothing there, since there's no `await` to configure. – Stephen Cleary Aug 24 '17 at 18:06
  • Weird. I have an API that uses ConfigureAwait(false) everywhere, and when I need a sync run, I simply do `var X = FuncAsync().ConfigureAwait(false).GetAwaiter().GetResult();`, and it doesn't deadlock in ASP; it works well. – victor Aug 24 '17 at 18:30
  • @victor: If that works, then `var X = FuncAsync().GetAwaiter().GetResult();` would also work. – Stephen Cleary Aug 24 '17 at 18:57
0

The object you are calling .Wait() on is also a task, and you have not configured it. Add .ConfigureAwait(false) to the return value from ExecuteQueryAsync.

Or, better yet, WebAPI can be a sync as well, so making the whole stack async will be better overall.