Получение токенов отмены в объявлениях Async.FromContinuations

Рассмотрим следующее определение

let test =
    Async.FromContinuations(
        fun (cont,econt,ccont) ->
            let rec inner () =
                async {
                    do printfn "looping..."
                    do! Async.Sleep 1000
                    return! inner ()
                }

            Async.Start(inner ())
            cont ())

Предположим, я хочу попробовать вычислить так

let cts = new CancellationTokenSource ()
Async.Start(test, cts.Token)
cts.Cancel()

Это, естественно, не остановит внутренний цикл, поскольку я не передал подходящий токен отмены. Можно ли каким-либо образом получить токен внешней отмены через Async.FromContinuations? Я мог бы переписать это, используя асинхронный построитель и Async.CancellationToken, но тогда я потерял бы возможность передавать продолжения внутреннему выражению.


person eirik    schedule 20.09.2012    source источник


Ответы (2)


что-то вроде этого?

let test =
    async {
        let! ct = Async.CancellationToken
        return! Async.FromContinuations(
            fun (cont,econt,ccont) ->
                let rec inner () =
                    async {
                        do printfn "looping..."
                        do! Async.Sleep 1000
                        return! inner ()
                    }

                Async.Start(inner (), cancellationToken = ct)
                cont ())
    }
let cts = new CancellationTokenSource ()
Async.Start(test, cts.Token)
cts.CancelAfter(1000)
person desco    schedule 20.09.2012
comment
Не забудьте удалить CTS, возможно, поместив его в предложение использования в асинхронном режиме. - person t0yv0; 21.09.2012
comment
@toyvo Согласен - отсутствие Dispose плохо. Но разве CTS не должен удаляться вызывающим абонентом (тот, кто его создал)? Вероятно, после звонка cts.Cancel()? (Я бы подумал, что вызов Dispose внутри async может быть неправильным.) - person Tomas Petricek; 21.09.2012
comment
@TomasPetricek да, ты прав. Я думал о случае, когда вы хотите выпустить CTS после завершения асинхронности, но тогда вам также нужно убедиться, что Cancel не вызывается после Dispose; это требует немного больше кода. - person t0yv0; 21.09.2012

Можете ли вы описать, что вы пытаетесь сделать? Если я правильно понял ваш код, вы хотите запустить функцию цикла inner в фоновом режиме, а затем параллельно продолжить выполнение остальной части рабочего процесса (используя вызов cont()).

Для этого не нужно Async.FromContinuations. Существует функция, которая делает именно это, а также обрабатывает исключения, токены отмены и т. д.

Я думаю, вы могли бы переписать свою программу следующим образом:

let test = 
    // The inner loop function from your example
    let rec inner () = async { 
        do printfn "looping..." 
        do! Async.Sleep 1000 
        return! inner ()  } 

    async { 
      // Start the inner loop as a child async 
      let! _ = Async.StartChild(inner())
      // ... continue doing other things in parllel if you wish
      do printfn "main body running..." }

Запуск и отмена вычисления выглядят как прежде:

let cts = new CancellationTokenSource () 
Async.Start(test, cts.Token) 
// This will cancel the 'inner' loop as well
cts.Cancel() 

Если вы вызовете Async.StartChild с помощью let!, то он запустит внутреннюю задачу, передаст ей токен отмены и т. д. Он вернет токен, который вы можете использовать позже, чтобы дождаться завершения дочерней задачи, но поскольку вы этого не делаете, я использовал _ узор.

person Tomas Petricek    schedule 21.09.2012
comment
Я искал способ получить токен отмены при определении рабочих процессов из продолжений аналогично тому, как примитивные объявления в FSharp.Core используют внутренний тип AsyncParamsAux. Так что внутренний код, который я дал в посте, в основном просто наполнитель. Я предполагаю, что для этой цели достаточно обернуть объявление Async.FromContinuations в выражение async {}. - person eirik; 01.12.2012