簡體   English   中英

為什么這個異步代碼有時會失敗,只有在沒有觀察到的情況下?

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

這是幾周來一直運行良好的原始代碼。 在我剛剛做的測試中,它在100次嘗試中失敗了0次。

using (var httpClient = new HttpClient())
{
    var tasks = new List<Task>();

    tasks.Add(httpClient.GetAsync(new Uri("..."))
        .ContinueWith(request =>
        {
            request.Result.Content.ReadAsAsync<IEnumerable<Foo>>()
                .ContinueWith(response =>
                {
                    foos = response.Result;
                });
        }));

    tasks.Add(httpClient.GetAsync(new Uri("..."))
        .ContinueWith(request =>
        {
            request.Result.Content.ReadAsAsync<Bar>()
                .ContinueWith(response =>
                {
                    bar = response.Result;
                });
        }));

    await Task.WhenAll(tasks);
}

此代碼在100次嘗試中失敗了9次,其中一個或兩個元組值為null

var APIresponses = await HttpClientHelper.GetAsync
    <
        IEnumerable<Foo>,
        Bar
    >
    (
        new Uri("..."),
        new Uri("...")
    );

foos = APIresponses.Item1;
bar = APIresponses.Item2;
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);
        });
}

public static async Task<Tuple<T1, T2>> GetAsync<T1, T2>(Uri URI1, Uri URI2)
{
    T1 item1 = default(T1);
    T2 item2 = default(T2);

    var httpClient = new HttpClient();
    var tasks = new List<Task>()
    {
        GetAsync<T1>(httpClient, URI1, response =>
        {
            item1 = response.Result;
        }),
        GetAsync<T2>(httpClient, URI2, response =>
        {
            item2 = response.Result;
        })
    };

    await Task.WhenAll(tasks);

    return Tuple.Create(item1, item2);
}

修改代碼看起來像這樣,它將再次失敗100次嘗試中的0。

    await Task.WhenAll(tasks);
    System.Diagnostics.Debug.WriteLine("tasks complete");
    System.Diagnostics.Debug.WriteLine(item1);
    System.Diagnostics.Debug.WriteLine(item2);

    return Tuple.Create(item1, item2);
}

我一直在看這個超過半個小時,但我沒有看到錯誤在哪里。 有人看到了嗎?

這段代碼:

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

返回一個任務,但永遠不會等待該任務(並且不會向其添加Continuation)。 因此,在Task.WhenAll返回之前,可能無法設置項目。

但是,原始解決方案似乎有同樣的問題。

我的猜測是你正在處理值類型,並且兩者都有競爭條件,但在第二個例子中,你很早就將值類型(雖然它們仍然是它們的默認值)復制到元組中。 凡在你的其他的例子,你等待足夠長的時間將其復制或使用它們,這樣的問題延續,設置值已經用完了。

要解決您對其他問題的評論,您很少需要將async / awaitContinueWith混合使用。 您可以在async lambdas的幫助下執行“fork”邏輯,例如,問題中的代碼可能如下所示:

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;

    // ...
}

編輯:不接受我自己的答案,但留待參考。 代碼工作 ,帶有catch: ContinueWith失去SynchronizationContext


感謝@jbl@MattSmith讓我走上正軌。

問題確實是Task.WhenAll不等待繼續。 解決方案是設置TaskContinuationOptions.AttachedToParent

所以這

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

變成了這個

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

有關MSDN的更多信息:嵌套任務和子任務

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM