繁体   English   中英

F#并行设计模式

[英]F# parallel design pattern

我试图在F#Console项目中并行运行一些相同的任务。

任务如下

  1. ConcurrentQueue取出表名(此队列包含我的程序需要处理的表名)
  2. 打开表的SqlDataReader
  3. 将SqlDataReader中的每一行写入StreamWriter
  4. 压缩StreamWriter创建的文件
  5. 重复1 - 4

所以基本上每个任务都是一个while循环(姿势作为递归)来连续处理表。 我想并行启动4个任务,另一件事是我想在控制台上用户按键停止执行,比如Enter键。 但只有当前任务完成第4步时才应停止执行。

我尝试了以下内容

let rec DownloadHelper (cq:ConcurrentQueue<Table>) sqlConn = 
    let success, tb = cq.TryDequeue()
    if success then
        printfn "Processing %s %s" tb.DBName tb.TBName
        Table2CSV tb.DBName tb.TBName sqlConn
        DownloadHelper cq sqlConn

let DownloadTable (cq:ConcurrentQueue<Table>) connectionString=
    use con = new SqlConnection(connectionString)
    con.Open()
    DownloadHelper cq con

let asyncDownloadTask = async { return DownloadTable cq connectionString}
let asyncMultiDownload =
    asyncDownloadTask
    |> List.replicate 4
    |> Async.Parallel
asyncMultiDownload
|>Async.RunSynchronously
|>ignore

上面的代码有两个问题,

  1. 它阻止了主线程,因此我不知道如何做击键部分
  2. 我不确定如何优雅地停止执行。

我的第二次尝试如下使用CancellationToken,

let tokenSource = new CancellationTokenSource()

let cq = PrepareJobs connectionString
let asyncDownloadTask = async {  DownloadTable cq connectionString}
let task = async {  
            asyncDownloadTask
            |> List.replicate 4
            |> Async.Parallel
            |>ignore}
let val1 = Async.Start(task, cancellationToken =tokenSource.Token)
Console.ReadLine() |> ignore
tokenSource.Cancel()
Console.ReadLine() |> ignore
0

但似乎我根本无法开始任务。

您的代码有三个问题。

首先, DownloadHelper应该只做一个表。 通过使其递归,您正在采取过多控制并抑制并行性。

其次,简单地将操作放在async表达式中并不会神奇地使其异步。 除非DownloadTable函数本身是异步的,否则代码将一直阻塞直到完成。

因此,当您并行运行四次下载时,一旦启动,无论取消令牌如何,它们都将全部运行完成。

第三,在你的第二个例子中,你使用Async.Parallel但是然后抛弃输出,这就是为什么你的task什么都不做! 我想你想要做的就是抛弃异步的结果 ,而不是异步本身。

这是我的代码版本,用于演示这些要点。

首先,一个占用时间的虚函数:

let longAtomicOperation milliSecs = 
    let sw = System.Diagnostics.Stopwatch()
    let r = System.Random()
    let mutable total = 0.0
    sw.Start()
    while sw.ElapsedMilliseconds < int64 milliSecs do
        total <- total + sin (r.NextDouble())
    // return something
    total

// test
#time
longAtomicOperation 2000
#time
// Real: 00:00:02.000, CPU: 00:00:02.000, GC gen0: 0, gen1: 0, gen2: 0

请注意,此功能不是异步 - 一旦启动它将运行完成。

现在让我们把它变成一个async

let asyncTask id = async {  
    // note that NONE of the operations are async
    printfn "Started %i" id
    let result = longAtomicOperation 5000 // 5 seconds
    printfn "Finished %i" id
    return result
    }

异步块中的所有操作都不是异步的,因此我们没有任何好处。

以下是并行创建四个任务的代码:

let fourParallelTasks = async {
    let! results = 
        List.init 4 asyncTask
        |> Async.Parallel
    // ignore
    return ()
    }

不会忽略Async.Parallel的结果,但Async.Parallel其分配给一个值,该值会强制运行任务。 async表达式作为一个整体返回单位。

如果我们测试它:

open System.Threading

// start the task
let tokenSource = new CancellationTokenSource()
do Async.Start(fourParallelTasks, cancellationToken = tokenSource.Token)

// wait for a keystroke
System.Console.WriteLine("press a key to cancel")
System.Console.ReadLine() |> ignore
tokenSource.Cancel()
System.Console.ReadLine() |> ignore

即使按下某个键,我们也会得到这样的输出,因为一旦启动,每个任务都将运行完成:

press a key to cancel
Started 3
Started 1
Started 2
Started 0
Finished 1
Finished 3
Finished 2
Finished 0

另一方面,如果我们创建一个串行版本,如下所示:

let fourSerialTasks = async {
    let! result1 = asyncTask 1
    let! result2 = asyncTask 2
    let! result3 = asyncTask 3
    let! result4 = asyncTask 4
    // ignore
    return ()
    }

然后,即使任务是原子的,也会在每个步骤之间测试取消令牌,这允许取消子序列任务。

// start the task
let tokenSource = new CancellationTokenSource()
do Async.Start(fourSerialTasks, cancellationToken = tokenSource.Token)

// wait for a keystroke
System.Console.WriteLine("press a key to cancel")
System.Console.ReadLine() |> ignore
tokenSource.Cancel()
System.Console.ReadLine() |> ignore

按下键时,可以在每个步骤之间取消上述代码。

要以四个批次的方式处理队列中的所有元素,只需将并行版本转换为循环:

let rec processQueueAsync() = async {
    let! result = processFourElementsAsync()
    if result <> QueueEmpty then
        do! processQueueAsync()
    // ignore
    return ()
    }

最后,对我来说,使用async 不是关于并行运行,而是编写非阻塞代码。 因此,如果您的库代码是阻塞的,那么异步方法不会带来太多好处。

要确保代码是非阻塞的,您需要在助手中使用异步版本的SqlDataReader方法,例如NextResultAsync

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM