0

I'm working on converting my code to async and I'm stumped with this problem. My CallRestAPI is not returning. I can debug every line of code, even down to the return e; but it then goes to the last bracket and then hangs. for code completeness, I moved the httpclient into this method so a complete code example.

from the UI Thread, I call - [Dim result = ExecuteDSSTP(params...)].

The code does not return from CallRestAPI when serializing. It seems to run all the way through, but doesn't return.

 public DataSet ExecuteDSSTP(string asStoredProcedure, params object[] aoParams)
    {
        return ExecuteDSSTPAsync(asStoredProcedure, aoParams).Result;
    }

public async Task<DataSet> ExecuteDSSTPAsync(string asStoredProcedure, params object[] aoParams)
    {

        dbUtilityResponse retVal = new dbUtilityResponse();
            
       
        retVal = await CallRestAPI<dbUtilityResponse>("ExecuteSTP", new DbUtilityRequest() {Params....});
      

        return retVal.ResultDataSet;
    }


   private static async Task<T> CallRestAPI<T>(string method, object body)
    {

            var byteArrayContent = new ByteArrayContent(SerializeBson(body));
            byteArrayContent.Headers.ContentType = new MediaTypeHeaderValue("application/bson");

            var _httpClient = new HttpClient();
            _httpClient.DefaultRequestHeaders.Accept.Clear();
            _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/bson"));
            _httpClient.DefaultRequestHeaders.Add("Connection", "keep-alive");
            var response2 = await _httpClient.PostAsync("https://myurl/" + method, byteArrayContent).ConfigureAwait(continueOnCapturedContext: false);

            var ms = new MemoryStream(await response2.Content.ReadAsByteArrayAsync());
            using (var reader = new BsonDataReader(ms))
            {
                var serializer = new JsonSerializer();
                var e = serializer.Deserialize<T>(reader);
                return e;
            }
       
    }
Teddy Higgins
  • 140
  • 10
  • 1
    just so you know. The method will in effect return at the first `await`. While the `return e` will set the returned task as completed. So what does the caller code looks like? How is the returned task handled? – JonasH Dec 07 '20 at 12:28
  • Are you using async methods all the way up the chain? – ProgrammingLlama Dec 07 '20 at 12:42
  • @john no, it's called from a sync UI method. I'll post more code! – Teddy Higgins Dec 07 '20 at 12:56
  • 2
    This looks like a deadlock. Why are you not calling: `var dataset = await ExecuteDSSTPAsync(//...` from your UI thread? – Johnathan Barclay Dec 07 '20 at 13:05
  • The UI thread in this app hasn't been converted to async yet. – Teddy Higgins Dec 07 '20 at 13:08
  • 1
    I believe you also need to use `ConfigureAwait(false)` inside your `ExecuteDSSTPAsync`. Since it's not used there it configures that call to resume on the UI thread, but it can't because the `UI` thread is busy waiting on the `.Result`. In general this is a very bad setup (it's not good to block the UI thread). Mistakes like this are prone to occur with a setup like this. –  Dec 07 '20 at 13:20

2 Answers2

0

It is likely this is due to a deadlock

ExecuteDSSTPAsync(asStoredProcedure, aoParams).Result

.Result will stop the current (i.e. UI) thread from executing until the task is completed. However, the task cannot complete until it has run the code after _httpClient.PostAsync, and it needs to do this on the same context as the caller, i.e. the UI thread. But it cannot do this, since the UI thread is stopped at .Result.

So, classic deadlock. Because of this it is recommended to avoid .Result since it so easily results in deadlocks like this.

You should instead use await all the way to the UI layer. At the UI layer you should also make sure to call await in a try/catch statement to ensure you catch any exception and can present to the user or otherwise deal with them.

JonasH
  • 28,608
  • 2
  • 10
  • 23
0

@Knoop's comment led me to the right answer. Working code is below! It appears I need to use ConfigureAwait(False)

I also used this answer See answer best practices

  public DataRowCollection ExecuteSTPCred(DBConnection aConnection, string dbcCredentials, string asStoredProcedure, params object[] aoParams)
    {
        return ExecuteSTPCredAsync(aConnection, dbcCredentials, asStoredProcedure, aoParams).Result;
    }


  public async Task<DataRowCollection> ExecuteSTPCredAsync(DBConnection aConnection, string dbcCredentials, string asStoredProcedure, params object[] aoParams)
    {

        dbUtilityResponse retVal = new dbUtilityResponse();
        
            retVal = await CallRestAPI<dbUtilityResponse>("ExecuteSTP", new DbUtilityRequest()
            {
                params...
            }).ConfigureAwait(false);

        return retVal.ResultDataSet.Tables[0].Rows;
    }


  private static async Task<T> CallRestAPI<T>(string method, object body)
    {

        var byteArrayContent = new ByteArrayContent(SerializeBson(body));
        byteArrayContent.Headers.ContentType = new MediaTypeHeaderValue("application/bson");

        var _httpClient = new HttpClient();
        _httpClient.DefaultRequestHeaders.Accept.Clear();
        _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/bson"));
        _httpClient.DefaultRequestHeaders.Add("Connection", "keep-alive");
        var response2 = await _httpClient.PostAsync("https://MyUrl/" + method, byteArrayContent).ConfigureAwait(continueOnCapturedContext: false);

        var ms = new MemoryStream(await response2.Content.ReadAsByteArrayAsync().ConfigureAwait(false));
        using (var reader = new BsonDataReader(ms))
        {
            var serializer = new JsonSerializer();
            return serializer.Deserialize<T>(reader);

        }

    }
Teddy Higgins
  • 140
  • 10