Why does this async code sometimes fail, and only when not observed?

后端 未结 3 1743
情书的邮戳
情书的邮戳 2021-01-19 15:59

This is the original code that had been running fine for a few weeks. In a test I just did, it failed 0 out of 100 attempts.

using (var httpClient = new Http         


        
相关标签:
3条回答
  • 2021-01-19 16:20

    To address the comment from to your other question, you very rarely need to mix async/await with ContinueWith. You can do the "fork" logic with help of async lambdas, e.g., the code from the question may look like this:

    using (var httpClient = new HttpClient())
    {
        Func<Task<IEnumerable<Foo>>> doTask1Async = async () =>
        {
            var request = await httpClient.GetAsync(new Uri("..."));
            return response.Content.ReadAsAsync<IEnumerable<Foo>>();
        };
    
        Func<Task<IEnumerable<Bar>>> doTask2Async = async () =>
        {
            var request = await httpClient.GetAsync(new Uri("..."));
            return response.Content.ReadAsAsync<IEnumerable<Bar>>();
        };
    
        var task1 = doTask1Async();
        var task2 = doTask2Async();
    
        await Task.WhenAll(task1, task2);
    
        var result1 = task1.Result;
        var result2 = task2.Result;
    
        // ...
    }
    
    0 讨论(0)
  • 2021-01-19 16:22

    Edit: unaccepting my own answer, but leaving it for reference. The code works, with a catch: ContinueWith loses the SynchronizationContext


    Thanks to @jbl and @MattSmith for putting me on the right track.

    The problem was indeed that Task.WhenAll does not wait on the continuations. The solution is to set TaskContinuationOptions.AttachedToParent.

    So this

    private static Task GetAsync<T>(HttpClient httpClient, Uri URI, Action<Task<T>> continuationAction)
    {
        return httpClient.GetAsync(URI)
            .ContinueWith(request =>
            {
                request.Result.EnsureSuccessStatusCode();
    
                request.Result.Content.ReadAsAsync<T>()
                    .ContinueWith(continuationAction);
            });
    }
    

    becomes this

    private static Task GetAsync<T>(HttpClient httpClient, Uri URI, Action<Task<T>> continuationAction)
    {
        return httpClient.GetAsync(URI)
            .ContinueWith(request =>
            {
                request.Result.EnsureSuccessStatusCode();
    
                request.Result.Content.ReadAsAsync<T>()
                    .ContinueWith(continuationAction, TaskContinuationOptions.AttachedToParent);
            }, TaskContinuationOptions.AttachedToParent);
    }
    

    More info available on MSDN: Nested Tasks and Child Tasks

    0 讨论(0)
  • 2021-01-19 16:29

    This code:

            request.Result.Content.ReadAsAsync<T>()
                .ContinueWith(continuationAction);
    

    returns a task, but that task is never awaited (and no Continuation is added to it). So the item's might not get set before Task.WhenAll returns.

    However, the original solution seems to have the same problem.

    My guess is that you are dealing with value types, and that both have a race condition, but in the 2nd example, you copy the value types early enough (while they are still their default value) into the Tuple. Where as in your other examples you wait long enough before copying them or using them such that the problem continuation that sets the values has run.

    0 讨论(0)
提交回复
热议问题