Как отменить отдельные асинхронные вычисления, выполняемые параллельно с другими, из события нажатия кнопки - PullRequest
1 голос
/ 02 мая 2011

Я подготовил следующий код 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 _ -> ()))

Ответы [ 2 ]

3 голосов
/ 02 мая 2011

Отмена потоков без сотрудничества, как правило, плохая практика, поэтому я бы не советовал делать это. См. Например эту статью . Это можно сделать, когда вы программируете непосредственно с 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 голос
/ 03 мая 2011

Как упоминал Томас, принудительная остановка потока - это плохая идея, и разработка чего-то, что не осознает, что поток может быть в состоянии остановить, поднимает флаги, на мой взгляд, но, если вы делаете долгий расчет, если вы используете какую-либо структуру данных, такую ​​как массив 2 или 3D, то одним из вариантов будет возможность установить это значение на null, но это нарушает многие концепции F #, так как то, над чем работает ваша функция, должно быть не только неизменяемый, но если есть какой-то массив, который будет изменен, то больше ничего не должно его менять.

Например, если вам нужно остановить поток, который обрабатывает файл (я должен был сделать это раньше), то, поскольку файл не может быть удален, так как он был открыт, я смог открыть его в блокноте, затем просто удалите все содержимое и сохраните его, и поток потерпел крах.

Итак, вы можете захотеть сделать что-то подобное для достижения своей цели, но я бы посоветовал вам пересмотреть свой дизайн и посмотреть, есть ли лучший способ сделать это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...