1

I have a Web API service that is used to retrieve and update a specific set of data (MyDataSet objects), and I am running into some confusion in using async/await when performing the following events:

  • Update MyDataSet with new values
  • Get MyDataSet (but only after the new values have been updated)

In my client, I have something similar to the following:

Harmony.cs

private async Task<string> GetDataSet(string request)
{
  using(var httpClient = new HttpClient())
  {
    httpClient.baseAddress = theBaseAddress;
    HttpResponseMessage response = await httpClient.GetAsync(request);
    response.EnsureSuccessStatusCode();
    return response.Content.ReadAsStringAsync().Result;
  }
}

private async Task PostDataSet<T>(string request, T data)
{
  using (var httpClient = new HttpClient())
  {
    client.BaseAddress = new Uri(theBaseAddress);
    HttpResponseMessage response = await client.PostAsJsonAsync<T>(request, data);
    response.EnsureSuccessStatusCode();
  }
}

internal MyDataSet GetMyDataSetById(int id)
{
  string request = String.Format("api/MyDataSet/GetById/{0}", id);
  return JsonConvert.DeserializeObject<MyDataSet>(GetDataSet(request).Result);
}

internal void UpdateDataSet(MyDataSet data)
{
  PostDataSet("api/MyDataSet/Update", data);
}

HarmonyScheduler.cs

internal void ScheduleDataSet()
{
  MyDataSet data = ...
  harmony.UpdateDataSet(data);
  MyDataSet data2 = harmony.GetMyDataSetById(data.Id);
}

There is a compiler warning in Harmony.cs UpdateDataSet because the call is not awaited and execution will continue before the call is completed. This is affecting the program execution, because data2 is being retrieved before the update is taking place.

If I were to make UpdateDataSet async and add an await to it, then it just moves things up the stack a level, and now HarmonyScheduler gets the warning about not being awaited.

How do I wait for the update to be complete before retrieving data2, so that I will have the updated values in the data2 object?

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
jkh
  • 3,618
  • 8
  • 38
  • 66
  • 1
    Short answer / quick fix: Can you add `.Result` to `harmony.UpdateDataSet(data);`? Long answer: If you are using `async / await` right it **should** move things up the stack. All the way ... it spreads like a virus. It should happen throughout the whole call stack. – hatcyl May 27 '15 at 04:11

3 Answers3

2

How do I wait for the update to be complete before retrieving data2, so that I will have the updated values in the data2 object?

The thing I see many people don't comprehend is the fact that using the TAP with async-await will infect your code like a plague.

What do I mean by that? Using the TAP will cause async to bubble up all the way to the top of your call stack, that is why they say async method go "all the way". That is the recommendation for using the pattern. Usually, that means that if you want to introduce an asynchronous API, you'll have to provide it along side a separate synchronous API. Most people try to mix and match between the two, but that causes a whole lot of trouble (and many SO questions).

In order to make things work properly, you'll have to turn UpdateDataSet and GetMyDataSetById to be async as well. The outcome should look like this:

private readonly HttpClient httpClient = new HttpClient();

private async Task<string> GetDataSetAsync(string request)
{
    httpClient.BaseAddress = theBaseAddress;

    HttpResponseMessage response = await httpClient.GetAsync(request);
    response.EnsureSuccessStatusCode();

    return await response.Content.ReadAsStringAsync();
}

private async Task PostDataSetAsync<T>(string request, T data)
{
    client.BaseAddress = new Uri(theBaseAddress);
    HttpResponseMessage response = await client.PostAsJsonAsync<T>(request, data);
    response.EnsureSuccessStatusCode(); 
}

internal async Task<MyDataSet> GetMyDataSetByIdAsync(int id)
{
    string request = String.Format("api/MyDataSet/GetById/{0}", id);
    return JsonConvert.DeserializeObject<MyDataSet>(await GetDataSetAsync(request));
}

internal Task UpdateDataSetAsync(MyDataSet data)
{
    return PostDataSetAsync("api/MyDataSet/Update", data);
}

Note - HttpClient is meant to be reused instead of a disposable, single call object. I would encapsulate as a class level field and reuse it. If you want to expose a synchronous API, do so using a HTTP library that exposes a synchronous API, such as WebClient.

Community
  • 1
  • 1
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
0

Always wait on the tasks right before you need its results. Its always better to wait on a task if it contains an await in its body. Warning : Do not use .Wait() or .Result

You would understand the concept better if you go through the control flow as explained Control Flow in Async Programs.

So in your case I would make all the functions accessing the async methods GetDataSet(string request) and PostDataSet<T>(string request, T data) with return type Task as awaitable.

Rohit Vipin Mathews
  • 11,629
  • 15
  • 57
  • 112
-1

Since you dont seem to expect any result back from the PostDataSet function you could just wait for it to complete.

PostDataSet("api/MyDataSet/Update", data).Wait();
Peter
  • 174
  • 8
  • That is a great way to introduce deadlocks. Never use `.Wait()` or `.Result` at the same time as async/await unless you know what you are doing (and if you have to be told to do it you don't know what you are doing) – Scott Chamberlain May 27 '15 at 04:59