Совместная отмена в F# с отменой продолжения

Возможно, у меня здесь 2 вопроса вместо одного, но тем не менее.

Я реализую совместную отмену, как предложено здесь. Вот мой тестовый код:

type Async with
    static member Isolate(f : CancellationToken -> Async<'T>) : Async<'T> =
        async {
            let! ct = Async.CancellationToken
            let isolatedTask = Async.StartAsTask(f ct)
            return! Async.AwaitTask isolatedTask
        }

let testLoop (ct: CancellationToken) = async {
    let rec next ix =
        if ct.IsCancellationRequested then ()
        else 
            printf "%i.." ix
            Thread.Sleep 10  
            next (ix+1)
    next 1
}

let cancellationSource = new CancellationTokenSource()
let onDone () = printfn "!! DONE"
let onError _ = printfn "!! ERROR"
let onCancel _ = printfn "!! CANCEL"

Async.StartWithContinuations (Async.Isolate testLoop, onDone, onError, onCancel, cancellationSource.Token)

Thread.Sleep(100)
cancellationSource.Cancel ()
Thread.Sleep(500)

Как видите, я запускаю async с продолжениями done, cancel и error. Если я запущу этот код как есть, я получу следующий вывод:

1..2..3..4..5..6..7..8..!! СДЕЛАНО

Если я немного обновлю метод Isolate следующим образом:

    static member Isolate(f : CancellationToken -> Async<'T>) : Async<'T> =
        async {
            let! ct = Async.CancellationToken
            let isolatedTask = Async.StartAsTask(f ct)
            let! x = Async.AwaitTask isolatedTask
            x
        }

Я получаю ожидаемый (сам по себе) вывод:

1..2..3..4..5..6..7..!! ОТМЕНА

Почему у нас такая разница в поведении?

Можно ли прервать testLoop, если он не отменяется в течение некоторого тайм-аута?


person ie.    schedule 02.06.2016    source источник


Ответы (1)


Блок async проверяет отмену Async.CancellationToken только до и после привязки (записано с использованием let!). Это означает, что когда токен отменяется, рабочий процесс отменяется только тогда, когда необходимо выполнить дополнительную работу.

Также стоит отметить, что isolatedTask сам по себе не отменяется в этом примере, потому что он просто завершается регулярно (используя if).

В твоем случае:

  • Когда вы используете только return!, задача регулярно возвращается, Async.AwaitTask регулярно возвращается, и после этого ничего не делается, поэтому рабочий процесс завершается.

  • Когда вы используете let!, за которым следует return, задача регулярно возвращается, а Async.AwaitTask регулярно возвращается, но затем let! проверяет отмену перед запуском return, и это отменяет рабочий процесс.

person Tomas Petricek    schedule 02.06.2016