【问题标题】:How to add timeout and retry to url call in C#?如何在 C# 中添加超时并重试 url 调用?
【发布时间】:2020-07-17 14:17:49
【问题描述】:

我有一个.tgz 文件,我需要在Testing 文件夹中给定一个网址下载该文件。我可以使用WebClient从网址成功下载.tgz文件。

下面是我的代码:

private void DownloadTGZFile(string url, string fileToDownload)
{
    using (var client = new WebClient())
    {
        client.DownloadFile(url + fileToDownload, "Testing/configs.tgz");
    }
}

我想看看如何在此调用中添加超时,以便如果 url 在特定时间内没有响应,那么它应该超时,但它可以重试 3 次然后放弃。另外,我想看看如何在此处使用 HttpClient 而不是 WebClient,因为它是较旧的 BCL 类,不推荐使用。

【问题讨论】:

标签: c# dotnet-httpclient polly


【解决方案1】:

要使用HttpClient 下载文件,您可以:

// Is better to not initialize a new HttpClient each time you make a request, 
// it could cause socket exhaustion
private static HttpClient _httpClient = new HttpClient()
{
    Timeout = TimeSpan.FromSeconds(5)
};

public async Task<byte[]> GetFile(string fileUrl)
{
    using (var httpResponse = await _httpClient.GetAsync(fileUrl))
    {
        // Throws an exception if response status code isn't 200
        httpResponse.EnsureSuccessStatusCode();
        return await httpResponse.Content.ReadAsByteArrayAsync();
    }
}

For more details about socket exhaustion with HttpClient

如您所见,要为Http 调用定义超时,您应该在创建新的HttpClient 时设置超时。


要为之前的代码实施重试策略,我会安装Polly NuGet package,然后:

public async Task<byte[]> GetFile(string fileUrl)
{
    return await Policy
       .Handle<TaskCanceledException>() // The exception thrown by HttpClient when goes in timeout
       .WaitAndRetryAsync(retryCount: 3, sleepDurationProvider: i => TimeSpan.FromMilliseconds(300))
       .ExecuteAsync(async () =>
       {
           using (var httpResponse = await _httpClient.GetAsync(fileUrl))
           {
               // Throws an exception if response status code isn't 200
               httpResponse.EnsureSuccessStatusCode();
               return await httpResponse.Content.ReadAsByteArrayAsync();
           }
       });
}

在这种情况下,我定义了 3 次重试,每次尝试之间的间隔为 300 毫秒。另请注意,我没有为每种Exception 定义重试,因为如果——例如——你输入了一个无效的URL,那么重试就是无稽之谈。

最后,如果你想将该字节数组保存到文件中,你可以这样做:

File.WriteAllBytes(@"MyPath\file.extension", byteArray);

【讨论】:

  • 感谢您的建议。现在这很有意义,我现在了解如何以更好的方式在 HttpClient 上使用超时。在您的示例中,您将返回 byte[] 那么如何在文件夹中下载此文件(.tgz 文件)?就我而言,我需要从特定文件夹中的 url 下载文件。这里可以吗?
  • 我在答案末尾添加了如何保存文件!
【解决方案2】:

您可以在不依赖外部库的情况下使用此函数。它适用于任何文件大小。

EDIT 传播TaskCanceledException 的版本。

public async Task<bool> DownloadFileAsync(string url,
    string destinationFile,
    TimeSpan timeout,
    int maxTries = 3,
    CancellationToken token = default)
{
    using (var client = new HttpClient { Timeout = timeout })
    {
        for (var i = 0; i < maxTries; i++, token.ThrowIfCancellationRequested())
        {
            try
            {
                var response = await client.GetAsync(url, token);
                if (!response.IsSuccessStatusCode)
                    continue;

                var responseStream = await response.Content.ReadAsStreamAsync();
                using (var outputStream = new FileStream(destinationFile, FileMode.Create, FileAccess.Write))
                {
                    await responseStream.CopyToAsync(outputStream, 8 * 1024, token);
                    return true;
                }
            }
            catch (HttpRequestException)
            {
                //ignore
            }
        }
        return false;
    }
}

【讨论】:

  • 你忘记Task.WaitAny(getTask);之前的await了吗?
  • 通过特殊返回值传播取消是非常规的。 standard 的方式是抛出一个OperationCanceledException
  • @TheodorZoulias 这个函数不是异步的,请查看MSDN
  • 是的,你是对的,它确实不是异步的。为什么不使用Task.WhenAny,避免阻塞线程?
  • @TheodorZoulias 我删除了第一个函数以避免混淆和非常规代码,并更新了第二个函数。感谢您的建议。
猜你喜欢
  • 1970-01-01
  • 2016-02-10
  • 2018-09-27
  • 2020-09-27
  • 1970-01-01
  • 1970-01-01
  • 2015-11-27
  • 1970-01-01
  • 2022-01-24
相关资源
最近更新 更多