生命游戏的递归函数不会在Async中运行

时间:2012-12-30 22:33:20

标签: f#

我正在尝试运行一个函数来更新用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

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:0)

Tomas 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次然后停止