F# sequence expressions bug (part 2)
Немного изменив предыдущий код, я был искренне удивлён результатами:
let xs = seq {
try yield! seq {
try yield! seq {
try yield ()
finally printfn "dispose::3"
}
finally printfn "dispose::2"
failwith "bar!"
}
finally printfn "dispose::1"
}
for _ in xs do ()
Вывод оказался следующим: o__O
dispose::3
dispose::2
dispose::1
То есть после исключения в finally отработал внешний finally и исключение успешно долетело до вызывающей стороны… А я всего лишь удалил бросание исключения в самом вложенном генераторе…
Чтобы ответить на этот вопрос, пришлось погрузиться в код, который F# генерирует для sequence expression’ов и оказалось, что работа с finally в корне отличается от итераторов C#. Интересно, что в сгенерированном коде F# не использует ни магический блок try { } fault { }
, ни даже стандартный try { } finally { }
. Обычно блоки finally внутренних последовательностей срабатывают, когда внешние последовательности делают им вызовы MoveNext()
. Если во время перебора любой из последовательностей возникает исключение, то оно проваливается безо всякой обработки прямо до вызывающей стороны, которая по соглашению вызывает .Dispose()
самой внешней последовательности, а та берёт на себя обязанность в правильном порядке вызвать .Dispose()
вложенных последовательностей.
Так почему код работает как и ожидается, не смотря на баг с исключениями в finally, описанный в предыдущем посте? Оказывается, всё очень просто: первые два finally выполняются во время вызовов MoveNext(), во втором возникает исключение, которое долетает до вызывающей стороны, которая в finally-блоке вызывает .Dispose()
последовательности, а уже она “диспоузит” незакрытые IEnumerator
‘ы. Изменив код следующим образом:
let xs = seq {
try yield! seq {
try yield! seq {
try yield ()
finally printfn "dispose::3"
failwith "foo!"
}
finally printfn "dispose::2"
failwith "bar!"
}
finally printfn "dispose::1"
}
for _ in xs do ()
Всё встаёт на свои места:
dispose::3
dispose::2
Теперь осталось поразмышлять, возможно ли это запилить простыми изменениями в стандартной библиотеке F#… =)))