【问题标题】:How to specify a timeout value on HttpWebRequest.BeginGetResponse without blocking the thread如何在 HttpWebRequest.BeginGetResponse 上指定超时值而不阻塞线程
【发布时间】:2010-01-23 07:32:19
【问题描述】:

我正在尝试异步发出 Web 请求。我的代码工作正常,除了一件事:似乎没有内置方法可以在 BeginGetResponse 上指定超时。 MSDN 示例清楚地显示了一个工作示例,但它的缺点是它们都以

SomeObject.WaitOne()

这再次清楚地表明它阻塞了线程。我将处于高负载环境中并且不能阻塞,但如果请求超过 2 秒,我还需要超时。除了创建和管理单独的线程池之外,框架中是否已经存在可以帮助我的东西?

开始示例:

我想要的是一种在我的超时参数到期后调用BeginGetResponse() 上的异步回调的方法,并带有一些表明发生超时的迹象。

看似明显的TimeOut 参数在异步调用中不受支持。 ReadWriteTimeout 参数在响应返回之前不会起作用。 最好使用非专有的解决方案。

编辑:

这是我想出的:在调用BeginGetResponse 之后,我创建了一个带有我的持续时间的Timer,这就是处理“开始”阶段的结束。现在,请求将完成并且我的“结束”阶段将被调用,或者超时期限将到期。

为了检测比赛并有一个获胜者,我称之为以线程安全的方式增加一个“完成”计数器。如果“超时”是第一个返回的事件,我会中止请求并停止计时器。在这种情况下,当调用“end”时,EndGetResponse 会引发错误。如果“结束”阶段首先发生,它会增加计数器并且“超时”会放弃中止请求。

这似乎像我想要的那样工作,同时还提供了可配置的超时。缺点是额外的计时器对象和我不努力避免的回调。我看到 1-3 个线程处理不同的部分(开始、超时、结束),所以它看起来像这样工作。而且我没有任何“等待”电话。

我是否错过了太多睡眠,或者我是否找到了一种方法来服务我的请求而不会阻塞?

int completed = 0;

this.Request.BeginGetResponse(GotResponse, this.Request);
this.timer = new Timer(Timedout, this, TimeOutDuration, Timeout.Infinite);

private void Timedout(object state)
{
    if (Interlocked.Increment(ref completed) == 1)
    {
        this.Request.Abort();
    }
    this.timer.Change(Timeout.Infinite, Timeout.Infinite);
    this.timer.Dispose();
}

private void GotRecentSearches(IAsyncResult result)
{
    Interlocked.Increment(ref completed);
}

【问题讨论】:

    标签: iis asynchronous httpwebrequest timeout


    【解决方案1】:

    您可以使用BackgroundWorkerHttpWebRequest 运行到一个单独的线程中,这样您的主线程仍然存在。所以,这个后台线程会被阻塞,但是第一个不会。

    在这种情况下,您可以使用ManualResetEvent.WaitOne(),就像在该示例中一样:HttpWebRequest.BeginGetResponse() 方法。

    【讨论】:

    • 如我的问题中所述,WaitOne 阻塞。我需要一个非阻塞的解决方案。
    • 是的,它阻塞了当前线程。这就是为什么我建议在 BackgroundThread 中运行它
    • 如果你这样做,它仍然在消耗一个 ThreadPool 线程。查看我的最新编辑。
    【解决方案2】:

    这是一个什么样的应用程序?这是一个服务进程/Web 应用程序/控制台应用程序吗?

    您如何创建工作负载(即请求)?如果您有一个需要完成的工作队列,您可以开始“N”个异步请求(使用您构建的超时框架),然后,一旦每个请求完成(超时或成功)您可以从队列中抓取下一个请求。

    这将因此成为生产者/消费者模式。

    因此,如果您将应用程序配置为最多有“N”个未完成的请求,您可以维护一个“N”个计时器池,您可以在请求之间重复使用(不释放)这些计时器。

    或者,您也可以使用 ThreadPool.SetTimerQueueTimer() 来管理您的计时器。线程池将为您管理计时器并在请求之间重用计时器。

    希望这会有所帮助。

    【讨论】:

    • 这是一个排名前 10 的网站,逻辑以超过 9K rps 的速度在每个页面上运行。阻止任何地方都不是一种选择。
    • 如果我理解正确,您是为 ASPX 页面收到的每个请求发出一个(或多个)异步 HTTPWebRequests 吗?如果是这样,您可以利用 ASPX 中的任何异步模式吗?如果有,您可以启动异步网络请求,并让它完成触发页面的完成。如果超时,您还可以使用错误代码或其他内容触发页面完成。这样你就不会持有线程池线程。
    【解决方案3】:

    似乎我原来的方法是最好的方法。

    【讨论】:

    • 你最终采取了什么方法?这个还是您找到了更好的方法?你有完整的源代码吗?
    【解决方案4】:

    如果你可以使用 async/await 那么

    private async Task<WebResponse> getResponseAsync(HttpWebRequest request)
            {
                var responseTask = Task.Factory.FromAsync(request.BeginGetResponse, ar => (HttpWebResponse)request.EndGetResponse(ar), null);
                var winner = await (Task.WhenAny(responseTask, Task.Delay(new TimeSpan(0, 0, 20))));
                if (winner != responseTask)
                {
                    throw new TimeoutException();
                }
                return await responseTask;
            }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-16
      • 1970-01-01
      • 2019-07-12
      • 1970-01-01
      • 2016-11-27
      • 2013-09-12
      • 1970-01-01
      • 2021-12-31
      相关资源
      最近更新 更多