Asynchronous &引用;节流;异步下载#
我正在尝试下载从我博客的xml备份中引用的3000多张照片。我遇到的问题是,如果这些照片中只有一张不再可用,整个异步就会被阻止,因为AsyncGetResponse不会超时 帮助我创建了一个版本的AsyncGetResponse,它在超时时会失败,但使用它会导致更多的超时,就好像请求只是排队超时一样。看起来所有的WebRequests都是“立即”启动的,唯一的方法是将超时设置为下载所有请求所需的时间:这并不好,因为这意味着我已经根据图像的数量调整了超时 我是否达到了香草Asynchronous &引用;节流;异步下载#,asynchronous,f#,timeout,Asynchronous,F#,Timeout,我正在尝试下载从我博客的xml备份中引用的3000多张照片。我遇到的问题是,如果这些照片中只有一张不再可用,整个异步就会被阻止,因为AsyncGetResponse不会超时 帮助我创建了一个版本的AsyncGetResponse,它在超时时会失败,但使用它会导致更多的超时,就好像请求只是排队超时一样。看起来所有的WebRequests都是“立即”启动的,唯一的方法是将超时设置为下载所有请求所需的时间:这并不好,因为这意味着我已经根据图像的数量调整了超时 我是否达到了香草async的极限?我应该转
async
的极限?我应该转而考虑被动扩展吗
这有点尴尬,因为我已经在这里询问了这段特定的代码,但我仍然没有让它按照我想要的方式工作 我认为一定有比使用超时更好的方法来发现文件不可用。我不太确定,但是如果找不到文件,有没有办法让它抛出异常?然后您可以将
async
代码包装到try。。使用
,您应该避免大多数问题
无论如何,如果您想编写自己的“并发管理器”,并行运行一定数量的请求并对剩余的未决请求进行排队,那么F#中最简单的选择就是使用代理(MailboxProcessor类型)。以下对象封装了该行为:
type ThrottlingAgentMessage =
| Completed
| Work of Async<unit>
/// Represents an agent that runs operations in concurrently. When the number
/// of concurrent operations exceeds 'limit', they are queued and processed later
type ThrottlingAgent(limit) =
let agent = MailboxProcessor.Start(fun agent ->
/// Represents a state when the agent is blocked
let rec waiting () =
// Use 'Scan' to wait for completion of some work
agent.Scan(function
| Completed -> Some(working (limit - 1))
| _ -> None)
/// Represents a state when the agent is working
and working count = async {
while true do
// Receive any message
let! msg = agent.Receive()
match msg with
| Completed ->
// Decrement the counter of work items
return! working (count - 1)
| Work work ->
// Start the work item & continue in blocked/working state
async { try do! work
finally agent.Post(Completed) }
|> Async.Start
if count < limit then return! working (count + 1)
else return! waiting () }
working 0)
/// Queue the specified asynchronous workflow for processing
member x.DoWork(work) = agent.Post(Work work)
类型ThrottlingAgentMessage=
|完成
|异步工作
///表示在中同时运行操作的代理。号码是什么时候
///超过“限制”的并发操作数,它们将排队并稍后处理
类型节流剂(限值)=
让代理=MailboxProcessor.Start(有趣的代理->
///表示代理被阻止时的状态
让rec等待()=
//使用“扫描”等待某些工作完成
代理扫描(功能)
|已完成->部分(正在工作(限制-1))
|(无)
///表示代理工作时的状态
和工作计数=异步{
尽管如此
//收到任何消息
让!msg=agent.Receive()
配味精
|已完成->
//减少工作项的计数器
返回!正在工作(计数-1)
|工作->
//启动工作项并在阻止/工作状态下继续
异步{尝试做!工作
最后,agent.Post(已完成)}
|>异步启动
如果计数<限制,则返回!工作(计数+1)
否则返回!正在等待()}
工作(0)
///将指定的异步工作流排队以进行处理
成员x.DoWork(工作)=代理岗位(工作)
没有什么是容易的。:)
我认为您遇到的问题是问题域的固有问题(而不仅仅是异步编程模型的问题,尽管它们之间确实存在一些交互)
假设你想下载3000张图片。首先,在您的.NET进程中,有一个类似System.NET.ConnectionLimit或我忘记其名称的东西,它将限制您的.NET进程可以同时运行的同时HTTP连接的数量(我认为默认值仅为“2”)。因此,您可以找到该控件并将其设置为更高的数字,这会有所帮助
但接下来,你的机器和互联网连接的带宽是有限的。因此,即使您可以尝试同时启动3000个HTTP连接,每个单独的连接也会因带宽管道限制而变慢。因此,这也会与超时相互作用。(这甚至不考虑服务器上有什么样的节流/限制。也许如果你发送3000个请求,它会认为你是DoS攻击,黑名单你的IP。)
因此,这确实是一个问题域,一个好的解决方案需要一些智能节流和流量控制,以便管理底层系统资源的使用方式
与另一个答案一样,F#Agent(MailboxProcessors)是编写此类节流/流量控制逻辑的良好编程模型
(即使如此,如果大多数图片文件都是1MB,但其中混入了1GB文件,那么单个文件可能会超时。)
无论如何,这与其说是对这个问题的回答,不如说是指出问题领域本身有多少内在复杂性。(也许这也暗示了为什么UI“下载管理器”如此受欢迎。)这里是Tomas答案的一个变体,因为我需要一个可以返回结果的代理
type ThrottleMessage<'a> =
| AddJob of (Async<'a>*AsyncReplyChannel<'a>)
| DoneJob of ('a*AsyncReplyChannel<'a>)
| Stop
/// This agent accumulates 'jobs' but limits the number which run concurrently.
type ThrottleAgent<'a>(limit) =
let agent = MailboxProcessor<ThrottleMessage<'a>>.Start(fun inbox ->
let rec loop(jobs, count) = async {
let! msg = inbox.Receive() //get next message
match msg with
| AddJob(job) ->
if count < limit then //if not at limit, we work, else loop
return! work(job::jobs, count)
else
return! loop(job::jobs, count)
| DoneJob(result, reply) ->
reply.Reply(result) //send back result to caller
return! work(jobs, count - 1) //no need to check limit here
| Stop -> return () }
and work(jobs, count) = async {
match jobs with
| [] -> return! loop(jobs, count) //if no jobs left, wait for more
| (job, reply)::jobs -> //run job, post Done when finished
async { let! result = job
inbox.Post(DoneJob(result, reply)) }
|> Async.Start
return! loop(jobs, count + 1) //job started, go back to waiting
}
loop([], 0)
)
member m.AddJob(job) = agent.PostAndAsyncReply(fun rep-> AddJob(job, rep))
member m.Stop() = agent.Post(Stop)
类型ThrottleMessage*AsyncReplyChannel(限制)=
let agent=MailboxProcessor(限制)
让res=jobs |>Seq.map(乐趣作业->代理.AddJob(作业))
|>异步并行
|>异步运行
代理。停止()
物件
它似乎工作正常…这里有一个现成的解决方案:
。我不确定这是否是最好的实现。但它的易用性非常好,而且由于我的应用程序不需要最高性能,所以对我来说,它工作得足够好。尽管它是一个库,但如果有人知道如何使它更好,那么让库在开箱即用的情况下表现最好总是一件好事,这样我们其他人就可以使用有效的代码,完成我们的工作 听起来好像少了一些“队列”。。。我不知道F#或它是如何工作的
async
,但如果它符合模型的话,使用CPS编写应该很容易。听起来越来越像你想要MailboxProcessor
而不是直接使用async
。你说有些照片不可用,那么服务器应该返回404(未找到)对吗?我的意思是,这不应该是超时之类的情况
static member RunJobs limit jobs =
let agent = ThrottleAgent<'a>(limit)
let res = jobs |> Seq.map (fun job -> agent.AddJob(job))
|> Async.Parallel
|> Async.RunSynchronously
agent.Stop()
res