Asynchronous 使用F#样式异步工作流的WebRequest超时
对于更广泛的上下文,这里是,它下载URL列表 在我看来,当使用Asynchronous 使用F#样式异步工作流的WebRequest超时,asynchronous,f#,httpwebrequest,Asynchronous,F#,Httpwebrequest,对于更广泛的上下文,这里是,它下载URL列表 在我看来,当使用use时,在F#中没有处理超时的好方法!response=request.AsyncGetResponse()样式URL获取。我几乎所有的东西都能正常工作(错误处理和异步请求和响应下载),避免了网站需要很长时间才能响应时出现的问题。我当前的代码只是无限期地挂起。我已经在我编写的等待300秒的PHP脚本上尝试过了。它一直在等待 我发现了两种“解决方案”,它们都是不可取的 WaitiaSyncResult+BeginGetResponse
use时,在F#中没有处理超时的好方法!response=request.AsyncGetResponse()
样式URL获取。我几乎所有的东西都能正常工作(错误处理和异步请求和响应下载),避免了网站需要很长时间才能响应时出现的问题。我当前的代码只是无限期地挂起。我已经在我编写的等待300秒的PHP脚本上尝试过了。它一直在等待
我发现了两种“解决方案”,它们都是不可取的
WaitiaSyncResult
+BeginGetResponse
喜欢伊尔贾恩的答案。问题是,如果您已将许多异步请求排入队列,则一些请求会在awaiiasyncresult
上被人为阻止。换句话说,发出请求的调用已经发出,但是幕后有什么东西阻止了调用。这导致在发出多个并发请求时,awaitiiasyncresult
上的超时提前触发。我的猜测是对单个域的请求数的限制,或者只是对总请求数的限制
为了支持我的怀疑,我编写了一个小WPF应用程序来绘制请求开始和结束的时间线。在我上面链接的代码中,请注意计时器在第49行和第54行(调用第10行)开始和停止。这是你的电话号码
当我将计时器移动到初始响应之后开始时(因此我只是对内容的下载进行计时)。注意,这是两个独立的运行,但除了计时器的启动位置之外,没有代码更改。不要在使用前直接测量开始时间
!response=request.AsyncGetResponse()
,之后我就有了它
为了进一步支持我的主张,我与他制定了一个时间表。这是显然,请求并不是在我告诉他们的时候开始的。
GetResponseStream
在新线程中
换句话说,同步请求和下载调用是在辅助线程中进行的。这是可行的,因为GetResponseStream
尊重WebRequest
对象上的Timeout
属性。但在这个过程中,我们失去了所有的等待时间,因为请求已在传输中,而响应尚未返回。我们不妨用C写它
问题
- 这是一个已知的问题吗
- 有什么好的解决方案可以利用F#异步工作流,并且仍然允许超时和错误处理
- 如果问题真的是我一次发出的请求太多,那么限制请求数量的最佳方法是使用
还是类似的东西信号量(5,5)
- 附带问题:如果你看过我的代码,你能看到我做过的任何愚蠢的事情并且可以修复吗
如果您有什么不明白的地方,请告诉我。AsyncGetResponse只需忽略发布的任何超时值。。。这是我们刚刚制定的解决方案:
open System
open System.IO
open System.Net
type Request = Request of WebRequest * AsyncReplyChannel<WebResponse>
let requestAgent =
MailboxProcessor.Start <| fun inbox -> async {
while true do
let! (Request (req, port)) = inbox.Receive ()
async {
try
let! resp = req.AsyncGetResponse ()
port.Reply resp
with
| ex -> sprintf "Exception in child %s\n%s" (ex.GetType().Name) ex.Message |> Console.WriteLine
} |> Async.Start
}
let getHTML url =
async {
try
let req = "http://" + url |> WebRequest.Create
try
use! resp = requestAgent.PostAndAsyncReply ((fun chan -> Request (req, chan)), 1000)
use str = resp.GetResponseStream ()
use rdr = new StreamReader (str)
return Some <| rdr.ReadToEnd ()
with
| :? System.TimeoutException ->
req.Abort()
Console.WriteLine "RequestAgent call timed out"
return None
with
| ex ->
sprintf "Exception in request %s\n\n%s" (ex.GetType().Name) ex.Message |> Console.WriteLine
return None
} |> Async.RunSynchronously;;
getHTML "www.grogogle.com"
开放系统
开放系统
开放系统.Net
类型请求=WebRequest的请求*AsyncReplyChannel
让请求代理=
MailboxProcessor.Start异步{
尽管如此
let!(请求(请求,端口))=收件箱。接收()
异步的{
尝试
let!resp=req.AsyncGetResponse()
港口回复
具有
|ex->sprintf“子%s\n%s中的异常”(ex.GetType().Name)ex.Message |>Console.WriteLine
}|>异步启动
}
让我们获取HTML url=
异步的{
尝试
让req=“http://”+url |>WebRequest.Create
尝试
使用!resp=requestAgent.PostAndAsyncReply((fun chan->Request(req,chan)),1000)
使用str=resp.GetResponseStream()
使用rdr=新的StreamReader(str)
还一些
请求中止()
Console.WriteLine“RequestAgent调用超时”
一无所获
具有
|ex->
sprintf“请求%s中的异常\n\n%s”(例如GetType().Name)例如Message |>Console.WriteLine
一无所获
}|>Async.RunSynchronously;;
getHTML“www.grogogle.com”
i、 我们正在委托给另一个代理,并调用它来提供异步超时。。。如果我们在指定的时间内没有收到代理的答复,我们将中止请求并继续。我看到我的其他答案可能无法回答您的特定问题。。。这里是另一个不需要使用信号量的任务限制器实现
open System
type IParallelLimiter =
abstract GetToken : unit -> Async<IDisposable>
type Message=
| GetToken of AsyncReplyChannel<IDisposable>
| Release
let start count =
let agent =
MailboxProcessor.Start(fun inbox ->
let newToken () =
{ new IDisposable with
member x.Dispose () = inbox.Post Release }
let rec loop n = async {
let! msg = inbox.Scan <| function
| GetToken _ when n = 0 -> None
| msg -> async.Return msg |> Some
return!
match msg with
| Release ->
loop (n + 1)
| GetToken port ->
port.Reply <| newToken ()
loop (n - 1)
}
loop count)
{ new IParallelLimiter with
member x.GetToken () =
agent.PostAndAsyncReply GetToken}
let limiter = start 100;;
for _ in 0..1000 do
async {
use! token = limiter.GetToken ()
Console.WriteLine "Sleeping..."
do! Async.Sleep 3000
Console.WriteLine "Releasing..."
} |> Async.Start
开放系统
类型IParallelLimiter=
抽象GetToken:unit->Async
类型消息=
|AsyncReplyChannel的GetToken
|释放
让我们开始计算吧=
出租代理人=
MailboxProcessor.Start(有趣的收件箱->
让牛顿()=
{新IDisposable与
成员x.Dispose()=inbox.Post Release}
让rec循环n=async{
let!msg=收件箱。无扫描
|msg->async.返回msg |>Some
回来!
配味精
|发布->
循环(n+1)
|GetToken端口->
port.Reply Async.Start
阅读您链接到的关于我的答案的评论——我提供了一些文档链接,这些文档描述了如何否决默认的两个连接限制。@ildjarn我以前看到过这些链接,但我继续探讨这个问题,因为我认为更改连接限制并不能解决一个更深层次的问题,即开发人员如何知道t发出启动请求的调用与请求a之间的不可见空闲时间