【问题标题】:Fire & Forget using WebClient by setting Tmeout通过设置超时使用 WebClient 触发并忘记
【发布时间】:2014-08-10 02:30:09
【问题描述】:

我有一个关于 WebClient 的问题,希望有人能帮助我。我知道这个问题被问了几次,但我找不到一个好的答案。

我有两种不同的情况需要调用 URL。 a) 我不需要 URL 的任何回复(Fire&Forget)。 b) URL 返回一个 JSON,所以我需要等待在服务器上完成处理才能获取我的数据。

我对 a) 有一些问题,我不知道我所做的是否有意义。为了实现 Fire&Forget,我将 WebClient 超时设置为一个“小”数字,500 毫秒(代码如下)。 我的问题是:

a) 这是一个好方法还是没有任何意义? b) 等待 500 毫秒是否有意义?我可以将超时设置为零吗? c) 这种方法安全吗?我不在乎处理完成需要多长时间,但我想确保我能够启动 Web 服务。下面的代码是否保证 Web 服务会被触发,如果没有,我会得到一个不是 Timneout 的异常?

非常感谢!

public static void SubmitUrl(string url)
{
    using (WebClient webClient = new WebClientEx(500))
    {
        try
        {
            Logger.Log(string.Format("Start Invoking URL: {0}", url));
            var response = webClient.OpenRead(url);
            response.Close();
            Logger.Log(string.Format("End Invoking URL: {0}", url));

        }
        catch (System.Net.WebException ex)
        {
            if (ex.Status != WebExceptionStatus.Timeout)
            {
                Logger.Log(string.Format("Exception Invoking URL: {0} \n {1}", url, ex.ToString()));
                throw;
            }
        }
        catch (System.Exception ex)
        {
            Logger.Log(string.Format("Exception Invoking URL: {0} \n {1}", url, ex.ToString()));
            throw;
        }
    }
}

public class WebClientEx : WebClient
{
    // Timeout in milliseconds
    public int timeout { get; set; }

    public WebClientEx(int timeout)
        : base()
    {
        this.timeout = timeout;
    }

    protected override WebRequest GetWebRequest(Uri address)
    {
        WebRequest wr = base.GetWebRequest(address);
        wr.Timeout = this.timeout;
            return wr;
        }
    }
}

【问题讨论】:

  • 您使用的是哪个 .NET 框架版本?

标签: c# webclient fire-and-forget


【解决方案1】:

将 WebClient 调用放在不同的线程中,这样它就不会阻塞主应用流程中的任何内容。 在这种情况下,Logger 必须是非常线程安全的。

    public static void SubmitUrl(string url)
    {
        Task.Factory.StartNew(() => SubmitUrlPrivate(url));
    }

    private static void SubmitUrlPrivate(string url)
    {
        using (var webClient = new WebClient())
        {
            try
            {
                Logger.Log(string.Format("Start Invoking URL: {0}", url));
                using (webClient.OpenRead(url))
                {
                    Logger.Log(string.Format("End Invoking URL: {0}", url));
                }
            }
            catch (WebException ex)
            {
                if (ex.Status != WebExceptionStatus.Timeout)
                {
                    Logger.Log(string.Format("Exception Invoking URL: {0} \n {1}", url, ex.ToString()));
                    throw;
                }
            }
            catch (Exception ex)
            {
                Logger.Log(string.Format("Exception Invoking URL: {0} \n {1}", url, ex.ToString()));
                throw;
            }
        }
    }

【讨论】:

  • 嗨彼得,谢谢你的回答。我不确定我可以或应该这样做。这些相同的方法用于 2 个不同的地方 a) 由 MVC 应用程序中的控制器/操作。据我所知,不建议将事物“推送”到 WebApp 中的另一个线程,因为您已经有一个线程。它也被 WCF 端点使用,该端点使用实例 PerCall,因此也使用线程。
  • 你不必担心世界会朝着一个有很多线程的方向发展,比如等待/异步模式。但另一个信息:在 Task 中,抛出的异常对父线程不可见,除非您像 Task.WaitAll() (阻塞调用)或使用全局 Task 异常处理技术捕获它。
  • 是的,但我认为 TPL 更易于使用。
  • 将超时设置为零怎么样?你认为那会安全吗?是否有可能由于超时以外的其他原因而无法调用 Web 服务?
  • 如果您将超时设置为零,那么如果远程服务没有立即(以光速)接受呼叫,那么您的呼叫将失败。
【解决方案2】:

我建议您不要使用线程池线程来执行受 IO 限制的工作,因为线程将大部分时间都花在阻塞 HTTP 请求上,所以根本没有必要。

此外,在 ASP.NET 中分离新线程而不注册它们是危险的,并且可能导致您的线程在应用程序池回收期间意外终止。

我建议您查看 HttpClient 类和使用 `async-await。

我还建议不要在大多数情况下使用火而忘记。如果请求失败或抛出异常会发生什么?不应该至少记录一下吗?

我会选择这样的:

private static async Task SubmitUrlAsync(string url)
{
    try
    {
        var httpClient = new HttpClient();
        Logger.Log(string.Format("Start Invoking URL: {0}", url));

        await httpClient.GetAsync(url);

        Logger.Log(string.Format("End Invoking URL: {0}", url));
    }
    catch (WebException ex)
    {
        if (ex.Status != WebExceptionStatus.Timeout)
        {
            Logger.Log(string.Format("Exception Invoking URL: 
                                {0} \n {1}", url, ex.ToString()));
            throw;
        }
    }
    catch (Exception ex)
    {
        Logger.Log(string.Format("Exception Invoking URL: 
                            {0} \n {1}", url, ex.ToString()));
        throw;
    }
}

await 关键字会将线程返回给线程池以处理更多请求,直到该方法完成,并且还将传播任何抛出的异常到等待的行。

【讨论】:

  • 那真是太好了。它几乎涵盖了我所有的要求。我喜欢这样一个事实,即我可以安全地记录我的异常。我真的很感谢你的cmets。非常感谢!
  • 没问题!很高兴我能帮上忙。
猜你喜欢
  • 1970-01-01
  • 2014-12-06
  • 2020-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-17
  • 2023-03-09
  • 2020-01-31
相关资源
最近更新 更多