Multithreading 在第一个完成的螺纹上连接?

Multithreading 在第一个完成的螺纹上连接?,multithreading,search,asynchronous,f#,Multithreading,Search,Asynchronous,F#,我正在用F#编写一系列图形搜索算法,并认为利用并行化会很好。我想并行执行几个线程,并获取第一个线程的结果。我已经有了一个实现,但它并不漂亮 两个问题:这类函数有标准名称吗?不是Join或JoinAll,而是JoinFirst?第二,有没有更惯用的方法 //implementation let makeAsync (locker:obj) (shared:'a option ref) (f:unit->'a) = async { let result = f()

我正在用F#编写一系列图形搜索算法,并认为利用并行化会很好。我想并行执行几个线程,并获取第一个线程的结果。我已经有了一个实现,但它并不漂亮

两个问题:这类函数有标准名称吗?不是Join或JoinAll,而是JoinFirst?第二,有没有更惯用的方法

//implementation
let makeAsync (locker:obj) (shared:'a option ref) (f:unit->'a) =
    async {
        let result = f()
        Monitor.Enter locker
        shared := Some result
        Monitor.Pulse locker
        Monitor.Exit locker
    }

let firstFinished test work =
    let result = ref Option.None
    let locker = new obj()
    let cancel = new CancellationTokenSource()    
    work |> List.map (makeAsync locker result) |> List.map (fun a-> Async.StartAsTask(a, TaskCreationOptions.None, cancel.Token)) |> ignore
    Monitor.Enter locker
    while (result.Value.IsNone || (not <| test result.Value.Value)) do
        Monitor.Wait locker |> ignore
    Monitor.Exit locker
    cancel.Cancel()
    match result.Value with
    | Some x-> x
    | None -> failwith "Don't pass in an empty list"
//end implentation

//testing
let delayReturn (ms:int) value = 
    fun ()->
        Thread.Sleep ms
        value

let test () =
    let work = [ delayReturn 1000 "First!"; delayReturn 5000 "Second!" ]
    let result = firstFinished (fun _->true) work
    printfn "%s" result
//实现
让makeAsync(locker:obj)(共享:'a选项参考)(f:unit->'a)=
异步的{
设结果=f()
监视器,进入储物柜
共享:=某个结果
监视器,脉冲锁定器
监视器,安全出口
}
让我们先完成测试工作=
让结果=参考选项。无
let locker=new obj()
let cancel=new CancellationTokenSource()
work |>List.map(makeAsync locker result)|>List.map(fun a->Async.startask(a,TaskCreationOptions.None,cancel.Token))|>忽略
监视器,进入储物柜
while(result.Value.IsNone | |)(不忽略
监视器,安全出口
取消
将结果.值与匹配
|一些x->x
|None->failwith“不传入空列表”
//最终实现
//测试
让delayReturn(ms:int)值=
乐趣()->
线程。睡眠ms
价值
let test()=
让工作=[delayReturn 1000“第一!”;delayReturn 5000“第二!”]
让结果=第一次完成(有趣->真实)工作
printfn“%s”结果

CancellationTokenSource
测试传递给每个异步对象,并让第一个计算有效结果的对象取消其他对象,这样是否可行

let makeAsync (cancel:CancellationTokenSource) test f =
  let rec loop() =
    async {
      if cancel.IsCancellationRequested then 
        return None
      else
        let result = f()
        if test result then
          cancel.Cancel()
          return Some result
        else return! loop()
    }
  loop()

let firstFinished test work =
  match work with
  | [] -> invalidArg "work" "Don't pass in an empty list"
  | _ ->
    let cancel = new CancellationTokenSource()    
    work
    |> Seq.map (makeAsync cancel test) 
    |> Seq.toArray
    |> Async.Parallel
    |> Async.RunSynchronously
    |> Array.pick id
这种方法有几个改进:1)它只使用了
async
(它没有与
任务混用,这是做同样事情的另一种选择--
async
在F#中更为惯用);2)没有共享状态,除了为此目的而设计的
CancellationTokenSource
;3) 干净的函数链接方法可以很容易地向管道添加额外的逻辑/转换,包括简单地启用/禁用并行性。

如果您可以在项目中使用“反应式扩展(Rx)”,joinFirst方法可以实现为:

let joinFirst (f : (unit->'a) list) = 
    let c = new CancellationTokenSource()
    let o = f |> List.map (fun i ->
                    let j = fun() -> Async.RunSynchronously (async {return i() },-1,c.Token)
                    Observable.Defer(fun() -> Observable.Start(j))
                    )
            |> Observable.Amb
    let r = o.First()
    c.Cancel()
    r
用法示例:

[20..30] |> List.map (fun i -> fun() -> Thread.Sleep(i*100); printfn "%d" i; i)
|> joinFirst |> printfn "Done %A"
Console.Read() |> ignore
更新:

使用邮箱处理器:

type WorkMessage<'a> = 
      Done of 'a
    | GetFirstDone of AsyncReplyChannel<'a>

let joinFirst (f : (unit->'a) list) = 
    let c = new CancellationTokenSource()
    let m = MailboxProcessor<WorkMessage<'a>>.Start(
              fun mbox -> async { 
                let afterDone a m = 
                    match m with
                    | GetFirstDone rc -> 
                        rc.Reply(a);
                        Some(async {return ()})
                    | _ -> None
                let getDone m = 
                    match m with
                    |Done a -> 
                        c.Cancel()
                        Some (async {
                                do! mbox.Scan(afterDone a)
                                })  
                    |_ -> None
                do! mbox.Scan(getDone)
                return ()
             } )
    f 
    |> List.iter(fun t -> try 
                            Async.RunSynchronously (async {let out = t()
                                                           m.Post(Done out)
                                                           return ()},-1,c.Token)
                          with
                          _ -> ())
    m.PostAndReply(fun rc -> GetFirstDone rc)

type WorkMessage不幸的是,
Async
没有为此提供内置操作,但我仍然使用F#Async,因为它们直接支持取消。使用
Async.start
启动工作流时,可以向其传递取消令牌,如果令牌被取消,工作流将自动停止

这意味着您必须显式地启动工作流(而不是使用
Async.Parallel
),因此必须手动编写同步。下面是一个简单版本的
Async.Choice
方法,它可以做到这一点(目前,它不处理异常):

一旦我们编写了这个操作(有点复杂,但只需编写一次),解决这个问题就相当容易了。您可以将操作编写为异步工作流,第一个操作完成后,它们将自动取消:

let delayReturn n s = async {
  do! Async.Sleep(n) 
  printfn "returning %s" s
  return s }

Async.Choice [ delayReturn 1000 "First!"; delayReturn 5000 "Second!" ]
|> Async.RunSynchronously

运行此操作时,它将只打印“returning First!”,因为第二个工作流将被取消。

对于.NET 4中的任务并行库,这称为
WaitAny
。例如,以下代码段创建10个任务并等待其中任何一个任务完成:

open System.Threading

Array.init 10 (fun _ ->
  Tasks.Task.Factory.StartNew(fun () ->
    Thread.Sleep 1000))
|> Tasks.Task.WaitAny

如果必须将结果存储在共享状态,为什么不让每个工作线程定期检查结果是否已生成,如果已生成,则结束?然后你就可以使用它了,你的代码应该是相对简单的。从架构上讲,我的搜索算法不需要知道它们并行化的细节,也不需要知道它们是否在并行运行。实际上,图搜索是高度递归的;共享状态用于单个节点的子节点。在所有节点上共享该状态将是。。。糟糕。我建议选择
async
Task
,并坚持下去,但不要将两者混用。它们是做同一件事的两种方式
async
在F#中更为惯用。我猜wait any方法只适用于任务,而不适用于任务类型。但这并不难实现。我会试试看,postWaitAny不会;t cancel other tasks:)这似乎是框架中最接近的任务,不过在取消其他任务之前,我需要添加一个循环来检查新完成的线程的结果是否通过了测试函数。希望它成熟一点,并在异步方面得到实现。
open System.Threading

Array.init 10 (fun _ ->
  Tasks.Task.Factory.StartNew(fun () ->
    Thread.Sleep 1000))
|> Tasks.Task.WaitAny