2012-12-30 31 views
1

我想运行一个函数来更新用f#编写的生命游戏函数的网格,并且所有东西都必须是递归的,没有任何可变。我想通过异步运行Update函数向我的表单添加一个暂停按钮,但是当我这样做时只更新了一个方块。但是,当我在没有异步的情况下浏览程序时,所有方块都会更新。任何想法为什么?生命游戏的递归函数不会在异步运行

let buttonGrid : Button list list = (Startup ar);; 

//transform buttongrid to int grid 
let rec bg2ig (bg:Button list list) = 
    let rec innerLoop (bl:Button list) = 
     match bl with 
     |[] -> [] 
     |x::xs -> if (x.Name = "0") then 0::(innerLoop xs) else 1::(innerLoop xs) 
    match bg with 
    |[] -> [] 
    |y::ys -> (innerLoop y)::(bg2ig ys) 

let Update (bg:Button list list)= 
    let ar = (bg2ig bg) 
    let rec innerUpdate (bg:Button list list)= 
     let rec arrayLoop (bl:Button list) y = 
      match bl with 
      |[] -> 0 
      |x::xs -> 
       let X = (15-xs.Length) 
       let n = (neighbors X y ar) 
       if (ar.[X].[y] = 0) then (if n=3 then buttonGrid.[X].[y].Name<-"1") else (if (n=2||n=3)=false then buttonGrid.[X].[y].Name<-"0") 
       if buttonGrid.[15-xs.Length].[y].Name="0" 
       then buttonGrid.[15-xs.Length].[y].BackColor <- Color.White 
       else buttonGrid.[15-xs.Length].[y].BackColor <- Color.Black 
       arrayLoop xs y 
     match bg with 
     |[] -> [] 
     |y::ys -> 
      ignore (arrayLoop y (15-ys.Length)) 
      innerUpdate ys 
    innerUpdate bg 

let Running = async { 
    let rec SubRun (x:int) = 
     ignore (Update buttonGrid) 
     if x = 1 then 
      SubRun 1 
     else 
      0 
    ignore (SubRun 1) 
    do! Async.Sleep(1000) 
    } 

let RunAll() = 
    Running 
    |> Async.RunSynchronously 
    |> ignore 
+0

“SubRun”有些奇怪'功能。看起来它永远不会终止('x'总是1)。 – wmeyer

+0

是的,我注意到,我之前已经睡过了,所以在执行更新之前它会等待1秒钟。我循环的原因是为了保持我的生命之旅。我想我可以使用Cancel令牌来停止递归循环,但我无法弄清楚它为什么不能像它应该那样工作。 –

+4

可能是从非UI线程访问WinForms对象的问题。尝试使用'Async.StartImmediate'而不是'Async.RunSynchronously'。 – wmeyer

回答

0

托马斯Petricek解决初始发行我有,但为了正确地做事,我最终以不同的方式解决问题。我认为我最初的问题可能是由于错误地更新表单或者根本没有更新表单造成的,因此它看起来非常错误。

最后我写我的异步函数这样

let rec async1(syncContext, form : System.Windows.Forms.Form, cancellationSource:CancellationTokenSource, (limit:int)) = 
    async { 
     do! Async.SwitchToContext(syncContext) 
     ignore (Update buttonGrid) 

     do! Async.SwitchToThreadPool() 

     do! Async.Sleep(300) 
     if limit > 1 then 
      ignore (Async.Start (async1(syncContext, form, cancellationSource, (limit-1)),cancellationSource.Token)) 
     else if limit = -1 then 
      ignore (Async.Start (async1(syncContext, form, cancellationSource, limit),cancellationSource.Token)) 
    } 

,然后我可以调用它像这样难熬启动和停止按钮

let b = new Button(Location=new Point(50,500), Text=("Run"), Width=100, Height=40) 
let btnPause = new Button(Location=new Point(150, 500), Text="Stop", Width=100, Height=40, Enabled=false) 
b.Click.Add(fun _ -> 
    let cancellationSource = new CancellationTokenSource() 
    b.Enabled <- false 
    btnPause.Enabled <- true 
    btnSave.Enabled <- false 
    btnLoad.Enabled <- false 
    btnStep.Enabled <- false 
    inputBox.Enabled <- false 
    btnPause.Click.Add(fun _ -> 
     b.Enabled <- true 
     btnPause.Enabled <- false 
     btnSave.Enabled <- true 
     btnLoad.Enabled <- true 
     btnStep.Enabled <- true 
     inputBox.Enabled <- true 
     cancellationSource.Cancel()) 
    ignore (Async.Start (async1(syncContext, form, cancellationSource, (int inputBox.Text)),cancellationSource.Token)) 
    ignore (inputBox.Text <- "0")) 

我还添加了一个一步按钮步进通过程序和一个输入框,我可以无休止地运行程序,直到取消令牌被调用或让它运行n次,然后停止

3

正如在评论中提到的,Async.RunSynchronously是这种情况下的错误功能。它在后台线程(这是错误的,因为你想要访问GUI元素)启动工作流,然后它阻塞调用线程,直到后台工作完成(这是错误的,因为你不想阻止GUI线程)。

您需要使用Async.StartImmediate,它可以在不阻塞的情况下开始工作当前线程(这将是GUI线程)。当工作流程的第一部分完成时(在Sleep之前),GUI线程可以自由地执行其他工作。在Sleep之后,工作流将再次在GUI线程上继续(这是由于StartImmediate而自动完成的),因此您可以再次访问GUI。

除此之外,您SubRun函数,它的实际的循环必须是异步的 - 所以我预计在循环的主要部分是这个样子:

let Running = async { 
    let rec SubRun (x:int) = 
     // Perform update and then sleep before recursive call 
     ignore (Update buttonGrid) 
     do! Async.Sleep(1000) 
     if x = 1 then 
      return! SubRun 1 
     else 
      return 0 } 

    // Start the loop and asynchronously ignore the result 
    SubRun 1 |> Async.Ignore 

let RunAll() = 
    // Start the computation immediately on the current threada 
    Running |> Async.StartImmediate 
+0

谢谢,你的答案帮助我解决了我的初始问题,但最终我写了不同的功能。 –