我准备了以下WinForms代码尽可能简单,以帮助回答我的问题。你可以看到我有一个开始按钮,它可以并行设置并运行3个不同的异步计算,每个计算都可以完成一些工作,然后用结果更新标签。我有3个取消按钮对应于并行运行的每个异步计算。如何连接这些取消按钮以取消相应的异步计算,同时允许其他按钮继续并行运行?谢谢!
open System.Windows.Forms
type MyForm() as this =
inherit Form()
let lbl1 = new Label(AutoSize=true, Text="Press Start")
let lbl2 = new Label(AutoSize=true, Text="Press Start")
let lbl3 = new Label(AutoSize=true, Text="Press Start")
let cancelBtn1 = new Button(AutoSize=true,Enabled=false, Text="Cancel")
let cancelBtn2 = new Button(AutoSize=true,Enabled=false, Text="Cancel")
let cancelBtn3 = new Button(AutoSize=true,Enabled=false, Text="Cancel")
let startBtn = new Button(AutoSize=true,Text="Start")
let panel = new FlowLayoutPanel(AutoSize=true, Dock=DockStyle.Fill, FlowDirection=FlowDirection.TopDown)
do
panel.Controls.AddRange [|startBtn; lbl1; cancelBtn1; lbl2; cancelBtn2; lbl3; cancelBtn3; |]
this.Controls.Add(panel)
startBtn.Click.Add <| fun _ ->
startBtn.Enabled <- false
[lbl1;lbl2;lbl3] |> List.iter (fun lbl -> lbl.Text <- "Loading...")
[cancelBtn1;cancelBtn2;cancelBtn3] |> List.iter (fun cancelBtn -> cancelBtn.Enabled <- true)
let guiContext = System.Threading.SynchronizationContext.Current
let work (timeout:int) = //work is not aware it is being run within an async computation
System.Threading.Thread.Sleep(timeout)
System.DateTime.Now.Ticks |> string
let asyncUpdate (lbl:Label) (cancelBtn:Button) timeout =
async {
let result = work timeout //"cancelling" means forcibly aborting, since work may be stuck in an infinite loop
do! Async.SwitchToContext guiContext
cancelBtn.Enabled <- false
lbl.Text <- result
}
let parallelAsyncUpdates =
[|asyncUpdate lbl1 cancelBtn1 3000; asyncUpdate lbl2 cancelBtn2 6000; asyncUpdate lbl3 cancelBtn3 9000;|]
|> Async.Parallel
|> Async.Ignore
Async.StartWithContinuations(
parallelAsyncUpdates,
(fun _ -> startBtn.Enabled <- true),
(fun _ -> ()),
(fun _ -> ()))
答案 0 :(得分:3)
非合作取消线程通常是一种不好的做法,所以我不建议这样做。请参阅示例this article。当您使用Thread
直接编程时(使用Thread.Abort)可以完成此操作,但.NET的现代并行/异步库(例如TPL或F#Async)都不会使用它。如果那真的是你需要的,那么你必须明确地使用线程。
更好的选择是更改work
功能,以便可以合作取消它。在F#中,这实际上只是意味着将其包含在async
内并使用let!
或do!
,因为这会自动插入取消支持。例如:
let work (timeout:int) = async {
do! Async.Sleep(timeout)
return System.DateTime.Now.Ticks |> string }
不使用async
(例如,如果函数是用C#编写的),您可以传递CancellationToken
值并使用它来检查是否请求取消(通过调用ThrowIfCancellationRequestsd
) 。然后,您可以使用Async.Start
开始三次计算(为每个计算创建一个新的CancellationTokenSource
)。
要做完所有事情,直到它们全部完成,我可能会创建一个简单的代理(触发一些事件,直到它收到指定数量的消息)。我认为还没有更直接的方法(因为Async.Parallel
对所有工作流使用相同的取消令牌)。
所以,我想这个答案的重点是 - 如果要取消work
,那么它应该知道这种情况,以便它可以适当地处理它。
答案 1 :(得分:1)
正如Tomas所提到的,强行停止一个线程是一个坏主意,设计一些没有意识到它的东西是一个能够阻止引发标志的线程,在我看来,但是,如果你做了很长的计算,如果您正在使用某些数据结构,例如2或3D数组,那么一个选项就是能够将其设置为null
,但这违反了F#的许多概念,因为您的函数正在处理应该不仅是不可变的,但如果有一些数组将要改变,那么没有别的东西可以改变它。
例如,如果你需要停止正在处理文件的线程(之前我必须这样做),那么,由于文件无法删除,因为它已打开,然后我就能打开它了在记事本中,然后只删除所有内容,并保存,线程崩溃。
所以,你可能想要做这样的事情来实现你的目标,但是,我建议你重新评估你的设计,看看是否有更好的方法来做到这一点。