我正在尝试运行一个函数来更新用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 :(得分: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次然后停止