簡體   English   中英

如何使用 .NET 異步代碼將 HTTP GET 發送到多個 URL 並獲得第一個結果?

[英]How can I send HTTP GETs to several URLs using .NET async code and get the first result?

我了解異步 javascript,但 aync .NET 有不同的方法,我仍然沒有正確理解它。

我有一個要檢查的 URL 列表。 我想異步檢查它們並獲得第一個返回特定狀態代碼的。 在這種情況下,我正在尋找狀態代碼 401(未授權),因為這表明這是一個登錄挑戰,這正是我所期待的。 所以我不能只使用Task.WaitAny因為我需要運行一些代碼來查看哪個首先與我的狀態代碼匹配。

誰能給我一個例子,說明如何在 aync 任務上運行回調,然后在找到所需內容后停止所有其他任務?

我在這個項目中使用 .NET 4,如果可能的話,我更願意堅持使用。 我安裝了System.Net.Http.HttpClient nuget 包。

更新:我已經把下面的代碼放在一起,我終於得到了正確的結果,除了我認為它正在等待每個任務 - 錯過了異步的全部意義。 不確定在內部任務中使用new Task()t.Wait() ,但它似乎是捕獲異常的唯一方法。 (異常發生在 DNS 失敗和連接超時 - 我不知道比捕獲和忽略異常更好的處理方法。)

關於改進此代碼以使其真正異步的任何建議?

    public async Task<ActionResult> Test() {
        //var patterns = GetPatterns();
        var patterns = "http://stackoverflow.com/,https://www.google.com,http://www.beweb.co.nz,https://outlook.office365.com/Microsoft-Server-ActiveSync,http://rubishnotexist.com".Split(",").ToList();

        var httpClient = new System.Net.Http.HttpClient();
        string result = "";
        CancellationTokenSource source = new CancellationTokenSource();
        CancellationToken cancellationToken = source.Token;
        var allTasks = new List<Task>();
        foreach (var pattern in patterns) {
            var url = pattern;

            Task task = new Task(() => {
                string answer = "";
                var st = DateTime.Now;
                var t = httpClient.GetAsync(pattern, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
                t.ContinueWith(d => {
                    if (!source.IsCancellationRequested) {
                        if (t.IsFaulted) {
                            answer = "Fault - " + " " + url;
                        } else if (d.Result.StatusCode == System.Net.HttpStatusCode.Unauthorized) {
                            // found it - so cancel all others
                            answer = "YES - " + d.Result.StatusCode + " " + url;
                            //source.Cancel();
                        } else {
                            answer = "No - " + d.Result.StatusCode + " " + url;
                        }
                    }
                    result += answer + " ("+(DateTime.Now-st).TotalMilliseconds+"ms)<br>";
                });
                try {
                    t.Wait();
                } catch (Exception) {
                    // ignore eg DNS fail and connection timeouts
                }
            });

            allTasks.Add(task);
            task.Start();
        }

        // Wait asynchronously for all of them to finish
        Task.WaitAll(allTasks.ToArray());

        return Content(result + "<br>DONE");
    }

在上面我沒有取消部分工作。 這是一個包括取消的版本:

    public async Task<ActionResult> Test2(string email) {
        var patterns = GetPatterns(email);
        patterns = "http://stackoverflow.com/,https://www.google.com,http://www.beweb.co.nz,https://outlook.office365.com/Microsoft-Server-ActiveSync,http://rubishnotexist.com".Split(",").ToList();
        var httpClient = new System.Net.Http.HttpClient();

        string result = "";
        CancellationTokenSource source = new CancellationTokenSource();
        CancellationToken cancellationToken = source.Token;
        var allTasks = new List<Task>();
        foreach (var pattern in patterns) {
            var url = pattern;

            Task task = new Task(() => {
                string answer = "";
                var st = DateTime.Now;
                var t = httpClient.GetAsync(pattern, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
                t.ContinueWith(d => {
                    if (!source.IsCancellationRequested) {
                        if (t.IsFaulted) {
                            answer = "Fault - " + " " + url;
                        } else if (d.Result.StatusCode == System.Net.HttpStatusCode.Unauthorized) {
                            // found it - so cancel all others
                            answer = "YES - " + d.Result.StatusCode + " " + url;
                            result += answer + " (" + (DateTime.Now - st).TotalMilliseconds + "ms)  <-- cancelled here <br>";
                            source.Cancel();
                        } else {
                            answer = "No - " + d.Result.StatusCode + " " + url;
                        }
                    } else {
                            answer = "cancelled - " + url;
                    }
                    result += answer + " (" + (DateTime.Now - st).TotalMilliseconds + "ms)<br>";
                });
                try {
                    t.Wait();
                } catch (Exception) {
                    // ignore
                }
            });

            allTasks.Add(task);
            task.Start();
        }

        // Wait asynchronously for all of them to finish
        Task.WaitAll(allTasks.ToArray());

        return Content(result + "<br>DONE");
    }

改用Task.WhenAll() ,然后檢查任務的結果。

為防止其他任務在任何人拋出異常時繼續執行,您可以創建一個CancellationToken (首先創建一個CancellationTokenSource ,然后使用其.Token )並將其傳遞給所有任務,如果失敗,則取消令牌; 如果引發任何異常,請參見如何取消並引發 Task.WhenAll 上的異常? 有關更多詳細信息和示例代碼。 然后所有的任務都會觀察令牌,並有選擇地偶爾明確檢查它,如果它被取消則退出。 他們還應該將它傳遞給支持它的那些方法,這樣他們又可以在令牌被取消時快速取消。

對於例外情況, 這個答案很好地涵蓋了它們。 如果你不想在調用代碼中拋出異常,你應該在每個任務創建中處理異常,但是你需要相應地修改上面的取消機制。 您可以改為只捕獲await Task.WhenAll()可能拋出的單個異常,然后觀察每個任務的Task.Exception屬性中拋出的所有異常,或者如果這是所需的結果,則忽略它們。


重新取消成功(來自評論) - 我想有很多方法可以做到,但一種可能是:

using (var cts = new CancellationTokenSource())
{
    var tasks = new List<Task<HttpStatusCode>>();

    foreach (var url in patterns)
    {
        tasks.Add(GetStatusCodeAsync(url, cts.Token));
    }

    while (tasks.Any() && !cts.IsCancellationRequested)
    {
        Task<HttpStatusCode> task = await Task.WhenAny(tasks);

        if (await task == HttpStatusCode.Unauthorized)
        {
            cts.Cancel();
            // Handle the "found" situation
            // ...
        }
        else
        {
            tasks.Remove(task);
        }
    }
}

然后將您的HttpClient代碼放在一個單獨的方法中:

private static async Task<HttpStatusCode> GetStatusCodeAsync(object url, CancellationToken token)
{
    try
    {
        // Your HttpClient code
        // ...
        await <things>;
        // (pass token on to methods that support it)
        // ...
        return httpStatusCode;
    }
    catch (Exception e)
    {
        // Don't rethrow if you handle everything here
        return HttpStatusCode.Unused; // (or whatever)
    }
}

暫無
暫無

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

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