我有一个包含大量小异步数据库查询的数组;例如:
// I actually have a more complex function that
// accepts name/value pairs for query parameters.
let runSql connString sql = async {
use connection = new SqlConnection(connString)
use command = new SqlCommand(sql, connection)
do! connection.OpenAsync() |> Async.AwaitIAsyncResult |> Async.Ignore
return! command.ExecuteScalarAsync() |> Async.AwaitTask
}
let getName (id:Guid) = async {
// I actually use a parameterized query
let querySql = "SELECT Name FROM Entities WHERE ID = '" + id.ToString() + "'"
return! runSql connectionString querySql
}
let ids : Guid array = getSixtyThousandIds()
let asyncWorkflows = ids |> Array.map getName
//...
现在,问题是:下一个表达式一次运行所有60K工作流,充斥着服务器。这导致许多SqlCommand
超时;它通常会导致客户端内存异常(F#交互)由于我不理解的原因(并且不需要理解它们)没有进行调查:
//...
let names =
asyncWorkflows
|> Async.Parallel
|> Async.RunSynchronously
我已经编写了一个粗略的函数来批量处理请求:
let batch batchSize asyncs = async {
let batches = asyncs
|> Seq.mapi (fun i a -> i, a)
|> Seq.groupBy (fst >> fun n -> n / batchSize)
|> Seq.map (snd >> Seq.map snd)
|> Seq.map Async.Parallel
let results = ref []
for batch in batches do
let! result = batch
results := (result :: !results)
return (!results |> List.rev |> Seq.collect id |> Array.ofSeq)
}
要使用此功能,我将Async.Parallel
替换为batch 20
(或其他整数值):
let names =
asyncWorkflows
|> batch 20
|> Async.RunSynchronously
这种方法运行得相当不错,但是我希望有一个系统在一个完成后立即启动每个新的异步,所以在每个上一批N大小完成之后,不是连续批量大小为N,我总是在等待N活跃SqlCommand
s(当然,直到我结束)。
问题:
我是否重新发明轮子?换句话说,是否有库函数可以执行此操作? (以某种方式探讨利用ParallelEnumerable.WithDegreeOfParallelism会不会有利可图?)
如果没有,我应该如何实现连续队列而不是一系列不连续的批次?
我主要不是在寻求改进现有代码的建议,但仍会感兴趣和感激地收到这些建议。
答案 0 :(得分:2)
FSharpx.Control
offers an Async.ParallelWithThrottle
function。我不确定它是否是it uses SemaphoreSlim
的最佳实现。但是易用性非常好,因为我的应用程序不需要顶级性能,所以它对我来说效果很好。虽然它是一个图书馆,如果有人知道如何使它变得更好,那么使图书馆成为最佳表现者总是一件好事,所以我们其他人可以只使用有效的代码并完成我们的工作!