如何解决如何从按钮单击事件中取消与其他进程并行运行的单个异步计算
|| 我已经准备了以下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,Text=\"Cancel\")
let cancelBtn3 = new Button(AutoSize=true,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 _ -> ()))
解决方法
通常,不合作地取消线程是一个坏习惯,因此,我不建议您这样做。例如,请参阅本文。当您直接使用
Thread
进行编程(使用Thread.Abort)时,可以完成此操作,但是.NET的现代并行/异步库(例如TPL或F#Async)都没有使用此功能。如果这确实是您所需要的,那么您将必须显式使用线程。
更好的选择是更改“ 2”功能,以便可以协作取消它。在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
,那么它应该知道这种情况,以便可以适当地对其进行处理。
,正如Tomas所说,强行停止线程是一个坏主意,而在我看来,设计一个没有意识到它能够停止的线程会引发标记,但是,如果您要进行长时间的计算,使用某种数据结构(例如2或3D数组),则可以将其设置为null
,但这违反了F#的许多概念,因为函数所使用的功能不仅应该是不可变的,但是,如果有一些要更改的数组,则别无其他更改。
例如,如果您需要停止正在处理文件的线程(之前我必须这样做),则由于无法删除该文件(已打开),因此可以在记事本中打开它,然后删除所有内容并保存,然后线程崩溃。
因此,您可能想要执行以下操作以实现您的目标,但是,我建议您重新评估设计,看看是否有更好的方法可以做到这一点。