0

We are working on a project developed in UWP(frontend) and REST-MVC-IIS(backend).

I was thinking on a theoretical scenario which might ensue:

From what I know, there is no way to guarantee the order in which requests will be processed and served by IIS.

So in a simple scenario, let's just assume this:

UI:

SelectionChanged(productId=1);

SelectionChanged(productId=2);

private async void SelectionChanged(int productId)
{
    await GetProductDataAsync(productId);
}

IIS:

GetProductDataAsync(productId=1) scheduled on thread pool

GetProductDataAsync(productId=2) scheduled on thread pool

GetProductDataAsync(productId=2) finishes first => send response to client

GetProductDataAsync(productId=1) finishes later => send response to client

As you can see, the request for productId=2 for whatever reason finished faster then the first request for productId=1.

Because the way async works, both calls will create two continuation tasks on the UI which will override each other if they don't come in the correct order since they contain the same data.

This can be extrapolated to almost any master-detail scenario, where it can happen to end up selecting a master item and getting the wrong details for it (because of the order in which the response comes back from IIS).

What I wanted to know is if there are some best practice to handle this kind of scenarios... lot's of solutions come to mind but I don't want to jump the gun and go for one implementation before I try to see what other options are on the table.

  • 1
    The two UI calls you show; are they literally 2 lines of code one following the other? If so, the second line will not begin execution until the first has completed, and the entire round trip to IIS is complete. – sellotape Nov 21 '16 at 17:41
  • Well, You missed the async which is being used for the two consecutive calls, that changes everything since they won't be processed sequential but in parallel. Well not in parallel per say but the result of both of those calls will be processed later on a continuation task for both calls, which means that the first call that returns will be processed first so if the second request finishes first(for whatever reason) then you end up in the situation described above, where the later call overrides the result of the earlier call. – Claudiu Cojocaru Nov 21 '16 at 22:19
  • 1
    If those 2 UI lines are consecutive they will _not be processed in parallel_. The await means exactly that: wait until the call is finished before continuing to the next line. Execution might well continue in the calling method, but it will not continue in the current method until the awaited task is complete. – sellotape Nov 21 '16 at 23:01
  • Well, if the method calling the await is async it won't stop, maybe I should have specified that all this is happening in an async context... Somehow I thought it's obvious since the question didn't made sense otherwise. – Claudiu Cojocaru Nov 21 '16 at 23:35
  • Also it's not that I have those two calls inside a method one after the other, what I wanted to recreate is what would happen with an async event handler that reacts to a selection changed event twice in a row. The method is async and uses await inside... Hope this makes things clearer. – Claudiu Cojocaru Nov 21 '16 at 23:39

2 Answers2

0

As you presented your code await GetProductDataAsync(productId=2); will always run after await GetProductDataAsync(productId=1); has completed. So, there is no race condition.

If your code was:

await Task.WhenAll(
    GetProductDataAsync(productId=1),
    GetProductDataAsync(productId=2))

Then there might be a race condition. And, if that's a problem, it's not particular to async-await but due to the fact that you are making concurrent calls.

If you wrap that code in another method and use ConfigureAwait(), you'll have only one continuation on the UI thread:

Task GetProductDataAsync()
{
    await Task.WhenAll(
        GetProductDataAsync(productId=1).ConfigureAwait(),
        GetProductDataAsync(productId=2).ConfigureAwait()
    ).ConfigureAwait();
}
Paulo Morgado
  • 14,111
  • 3
  • 31
  • 59
  • I've edited my original post, I've explained better the situation. It's not about race conditions, it's about the order in witch the call backs come back from the iis after being processed. – Claudiu Cojocaru Nov 22 '16 at 08:10
0

I think I get what you're saying. Because of the async void eventhandler, nothing in the UI is awaiting the first call before the second. I am imagining a drop down of values and when it changes, it fetches the pertinent data.

Ideally, you would probably want to either lock out the UI during the call or implement a cancellationtoken.

If you're just looking for a way to meter the calls, keep reading...

I use a singleton repository layer in the UWP application that handles whether or not to fetch the data from a web service, or a locally cached copy. Additionally, if you want to meter the requests to process one at a time, use SemaphoreSlim. It works like lock, but for async operations (oversimplified simile).

Here is an example that should illustrate how it works...

public class ProductRepository : IProductRepository
{
    //initializing (1,1) will allow only 1 use of the object
    static SemaphoreSlim semaphoreLock = new SemaphoreSlim(1, 1);
    public async Task<IProductData> GetProductDataByIdAsync(int productId)
    {
        try
        {
            //if semaphore is in use, subsequent requests will wait here
            await semaphoreLock.WaitAsync();
            try
            {
                using (var client = new HttpClient())
                {
                    client.BaseAddress = new Uri("yourbaseurl");
                    client.DefaultRequestHeaders.Accept.Clear();
                    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                    string url = "yourendpoint";
                    HttpResponseMessage response = await client.GetAsync(url);
                    if (response.IsSuccessStatusCode)
                    {
                        var json = await response.Content.ReadAsStringAsync();
                        ProductData prodData = JsonConvert.DeserializeObject<ProductData>(json);
                        return prodData;
                    }
                    else
                    {
                        //handle non-success
                    }
                }
            }
            catch (Exception e)
            {
                //handle exception
            }
        }
        finally
        {
            //if any requests queued up, the next one will fire here
            semaphoreLock.Release();
        }
    }
}
Community
  • 1
  • 1
Mark W
  • 1,050
  • 7
  • 15