UWP + IIS +异步行为

时间:2016-11-21 14:17:48

标签: c# iis async-await

我们正在开发一个在UWP(前端)和REST-MVC-IIS(后端)中开发的项目。

我正在考虑可能会出现的理论情景:

据我所知,无法保证IIS处理和提供请求的顺序。

所以在一个简单的场景中,让我们假设:

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

正如您所看到的,productId=2因任何原因的请求比productId=1的第一次请求更快完成。

由于异步的工作方式,两个调用都会在UI上创建两个连续任务,如果它们的顺序不正确,它们会相互覆盖,因为它们包含相同的数据。

这可以推断到几乎任何主 - 细节场景,最终可能会选择一个主项目并获取错误的详细信息(因为响应从IIS返回的顺序)。

我想知道的是,如果有一些最佳实践来处理这种情况......很多解决方案都会浮现在脑海中但我不想在我试图看到之前先试一试桌面上还有其他选择。

2 个答案:

答案 0 :(得分:0)

在您提交代码时await GetProductDataAsync(productId=2);将始终在await GetProductDataAsync(productId=1);完成后运行。所以,没有竞争条件。

如果你的代码是:

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

然后可能会有竞争条件。而且,如果这是一个问题,那么async-await并不特别,但由于您正在进行并发呼叫。

如果您将该代码包装在另一个方法中并使用ConfigureAwait(),那么您在UI线程上只有一个延续:

Task GetProductDataAsync()
{
    await Task.WhenAll(
        GetProductDataAsync(productId=1).ConfigureAwait(),
        GetProductDataAsync(productId=2).ConfigureAwait()
    ).ConfigureAwait();
}

答案 1 :(得分:0)

我想我得到你所说的。由于async void eventhandler,UI中没有任何内容等待第二次调用之前的第一次调用。我想象的是值的下降,当它发生变化时,它会获取相关的数据。

理想情况下,您可能希望在通话期间锁定用户界面或实施cancellationtoken

如果您只想找到一种计算电话的方法,请继续阅读......

我在UWP应用程序中使用单独的存储库层来处理是否从Web服务或本地缓存的副本获取数据。此外,如果要计量一次处理一个请求,请使用SemaphoreSlim。它的作用类似于锁,但用于异步操作(过度简化的明喻)。

这是一个应该说明它如何运作的例子......

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();
        }
    }
}