Как получить полезную стеклу при тестировании рабочих процессов F # async - программирование

Как получить полезную стеклу при тестировании рабочих процессов F # async

Я бы хотел протестировать следующий рабочий процесс async (с помощью NUnit + FsUnit):

let foo = async {
  failwith "oops"
  return 42
}

Я написал для него следующий тест:

let [<Test>] TestFoo () =
  foo
  |> Async.RunSynchronously
  |> should equal 42

Так как foo throws, я получаю следующую стеквую строку в бегуне unit test:

System.Exception : oops
   at Microsoft.FSharp.Control.CancellationTokenOps.RunSynchronously(CancellationToken token, FSharpAsync`1 computation, FSharpOption`1 timeout)
   at Microsoft.FSharp.Control.FSharpAsync.RunSynchronously(FSharpAsync`1 computation, FSharpOption`1 timeout, FSharpOption`1 cancellationToken)
   at ExplorationTests.TestFoo() in ExplorationTests.fs: line 76

К сожалению, stacktrace не говорит мне, где было создано исключение. Он останавливается при RunSynchronously.

Где-то я слышал, что Async.Catch волшебным образом восстанавливает стек, поэтому я скорректировал свой тест:

let [<Test>] TestFooWithBetterStacktrace () =
  foo
  |> Async.Catch
  |> Async.RunSynchronously
  |> fun x -> match x with 
              | Choice1Of2 x -> x |> should equal 42
              | Choice2Of2 ex -> raise (new System.Exception(null, ex))

Теперь это уродливо, но, по крайней мере, он создает полезную стек:

System.Exception : Exception of type 'System.Exception' was thrown.
  ----> System.Exception : oops
   at Microsoft.FSharp.Core.Operators.Raise(Exception exn)
   at ExplorationTests.TestFooWithBetterStacktrace() in ExplorationTests.fs: line 86
--Exception
   at Microsoft.FSharp.Core.Operators.FailWith(String message)
   at [email protected](Unit unitVar) in ExplorationTests.fs: line 71
   at [email protected](AsyncParams`1 args)

На этот раз stacktrace точно показывает, где произошла ошибка: [email protected] 71

Есть ли способ избавиться от Async.Catch и соответствия между двумя вариантами, все еще получая полезные стеки? Есть ли лучший способ структурирования асинхронных рабочих процессов?

4b9b3361

Ответ 1

Так как Async.Catch и ретронирование исключения, кажется, единственный способ получить полезную стеклу, я придумал следующее:

type Async with
  static member Rethrow x =
    match x with 
      | Choice1Of2 x -> x
      | Choice2Of2 ex -> ExceptionDispatchInfo.Capture(ex).Throw()
                         failwith "nothing to return, but will never get here"

Примечание "ExceptionDispatchInfo.Capture(ex).Throw()". Что самое приятное, можно восстановить исключение без искажения его stacktrace (downside: доступно только с .NET 4.5).

Теперь я могу переписать тест "TestFooWithBetterStacktrace" следующим образом:

let [<Test>] TestFooWithBetterStacktrace () =
  foo
  |> Async.Catch
  |> Async.RunSynchronously
  |> Async.Rethrow
  |> should equal 42

Тест выглядит намного лучше, повторный код не сосать (как и раньше), и я получаю полезные стеки в тестовом бегуне, когда что-то идет не так.

Ответ 2

Цитата из некоторых писем, которые я отправил Дону Симу некоторое время назад:

Опыт отладки должен улучшиться, если вы попытаетесь установить "Catch First Chance Exceptions" в Debug → Исключения → Исключения CLR. выключение "Just My Code" также может помочь.

и

Right. С async {...}, вычисления не привязаны к стеку, следовательно в некоторых местах необходимо вернуть исключения, чтобы вернуть их обратно правая нить.

Разумное использование Async.Catch или другая обработка исключений также может помощь.