Amir Popovich
Amir Popovich

Reputation: 29836

Implementing a synchronous method with calling the <asyncmethod>.Result

I have this async method:

public async Task<RES> PostAsync<RES>(string url, string content) where RES : new()
{
    using (var client = new HttpClient())
    {
        HttpResponseMessage message = await client.PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json"));
        var readAsStringAsync = await message.Content.ReadAsStringAsync();
        return await readAsStringAsync.FromJsonAsync<RES>(mySerializerSettings);
    }
}

Where FromJsonAsync is implemented as an extension method:

public async static Task<T> FromJsonAsync<T>(this string data, JsonSerializerSettings settings) where T : new()
{
    return (T)(await JsonConvert.DeserializeObjectAsync<T>(data, settings));
}

Now I want to add a regular synchronous Post method and I thought the implementation would be:

public RES Post<RES>(string url, string content) where RES : new()
{
    return PostAsync<RES>(url, content).Result;
}

But this doesn't really work. I see that the request is sent via a Http sniffer and I get a response back, but I get stuck when debugging and can't continue.

BTW, this does work (with Result instead of await):

public RES Post<RES>(string url, string content) where RES : new()
{
    using (var client = new HttpClient())
    {
        HttpResponseMessage message = client.PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json")).Result;
        var readAsStringAsync = message.Content.ReadAsStringAsync().Result;
        return readAsStringAsync.FromJson<RES>(mySerializerSettings);
    }
}

Where FromJson is implemented as an extension method:

public static T FromJson<T>(this string data, JsonSerializerSettings settings) where T : new()
{
    return (T)JsonConvert.DeserializeObject<T>(data, settings);
}

The application is a web backend (WebApi).

What am I doing wrong?

Upvotes: 1

Views: 1373

Answers (3)

i3arnon
i3arnon

Reputation: 116538

You probably have a deadlock on your hands.

Asp.net uses a SynchronizationContext to post continuations back to the request context. If the context is blocked (like it is in your case on PostAsync<RES>(url, content).Result) then the continuation can't be executed and so the async method can't complete and you have a deadlock.

You can avoid it by using ConfigureAwait(false):

public async Task<RES> PostAsync<RES>(string url, string content) where RES : new()
{
    using (var client = new HttpClient())
    {
        HttpResponseMessage message = await client.PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json"));
        var readAsStringAsync = await message.Content.ReadAsStringAsync().ConfigureAwait(false);
        return await readAsStringAsync.FromJsonAsync<RES>(mySerializerSettings).ConfigureAwait(false);
    }
}

But it's better to just avoid blocking synchronously on async code to begin with and having two different versions for sync and async.

Upvotes: 2

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149538

Although possible, I wouldn't use the answer provided by @i3arnon. Generally, you shouldn't block on async code. Although ConfigureAwait(false) does work, it can lead to confusion in your code-base where other developers may also end up blocking using .Result, without using ConfigureAwait or understanding the implications of that.

Instead, expose synchronous methods which are really synchronous:

public RES Post<RES>(string url, string content) where RES : new()
{
    using (var client = new WebClient())
    {
        client.Headers[HttpRequestHeader.ContentType] = "application/json";
        var result = client.UploadString(url, content);
        return JsonConvert.DeserializeObject<RES>(result, jsonSerializerSettings);
    }
}

Upvotes: 2

Harald Coppoolse
Harald Coppoolse

Reputation: 30454

It seems you have a non-async function and you want to start a task that will call PostAsync and wait for this task to finish and return the result of the Task. Is this your problem?

  • To start a Task, use Task.Run( () => ...);
  • To wait for the Task use Task.Wait(...);
  • To see if the task stopped because of an exception: Task.IsFaulted
  • The result of the task is in Task.Result

Your code could be:

public async Task<RES> PostAsync<RES>(string url, string content) where RES : new()
{
    // start the task that will call PostAsync:
    var postTask = Task.Run( () => PostAsync(url, content));
    // while this task is running you can do other things
    // once you need the result: wait for the task to finish:
    postTask.Wait();
    // If needed check Task.IsFaulted / Task.IsCanceled etc. to check for errors

    // the returned value is in Task.Result:
    return postTask.Result;
}

Upvotes: 0

Related Questions