Управление ресурсами и конструкция try...finally

Последнее обновление: 05.01.2024

Нередко в программе приходится работать с различными ресурсами типа файловых и сетевых потоков, которые надо явным образом закрывать после работы. В общем случае проблемы нет - мы открыли файл, поработали с ним и закрыли. Однако при работе с подобными ресурсами могут возникать исключения, что может привести к тому, что программа используемый ресурс должным образом не будет освобожден. И конструкция try...finally как раз позволяет выполнить освобождение ресурсов, даже если при работе с ними возникло исключением. Данную конструкцию можно применять при работе с файловыми, сетевыми потоками, в общем с различными ресурсами, для которых требуется освобождение, например, закрытие потока. Эта конструкция имеет следующий синтаксис:

try
    код_работы_с_ресурсом
finally
    код_особождения_ресурса

В блок try помещается код, который потенциально может сгенерировать исключение. В блоке finally происходит освобождение использованного ресурса. Причем блок finally выполняется даже в том случае, если в блоке try возникло исключение.

Например, возьмем следующую программу:

let writeList (items: string list) startIndex endIndex =
   let writer: System.IO.StreamWriter = new System.IO.StreamWriter("test.txt")
   try
      for i = startIndex to endIndex do
          writer.WriteLine(items[i])
      ()    // блок try возвращает значение типа unit
   finally
      writer.Flush()
      printfn "Закрытие потока"
      writer.Close()

let people = ["Tom"; "Bob"; "Sam"]
writeList people 0 3
printfn "Запись завершена"

Здесь определена функция writeList, которая записывает в файл список строк. Список строк передается в качестве первого параметра. Второй и третий параметры представляют соответственно начальный и конечный индексы элеменетов для записи в списке. Для записи в файл применяется встроенный тип .NET System.IO.StreamWriter. Через конструктор он принимает путь к файлу, в который надо выполнить запись - в данном случае это файл с именем "test.txt", который будет располагаться в текущей папке программы.

Внутри функции определяется конструкция try...finally. В блоке try в цикле проходим по элементам списка между индексами startIndex и endIndex и элемент списка между этими индексами записываем в файл с помощью функции WriteLine

writer.WriteLine(items[i])

В конце блока try возвращается значение типа unit - ()

Переданные индексы startIndex и endIndex могут быть за пределами допустимого диапазона для переданного списка. Соответственно, если как минимум один из индексов вне диапазона, то при работе программы возникнет исключение.

В блоке finally осуществляем закрытие ресурсов, которые связаны с объектом StreamWriter, то есть закрытие файлового потока.

finally
    writer.Flush()
    printfn "Закрытие потока"
    writer.Close()

Здесь начала сбрасываем все незаписанные данные из буфера в файл с помощью метода writer.Flush(), затем выводим на консоль диагностическое сообщение и в конце собственно закрываем файловый поток методом writer.Close()

И если мы передадим в функцию writeList нейдествительный индекс, как в следующем случае:

let people = ["Tom"; "Bob"; "Sam"]
writeList people 0 3   //  индекс 3 - недействительный

то в блоке try возникнет исключение, однако даже несмотря на этого будет выполняться блок finally, который закроет файловый поток.

Например, при выполнении выше расмотренной программы мы получим консольный вывод наподобие следующего:

Закрытие потока
System.ArgumentException: Индекс вышел за границы диапазона элементов списка. (Parameter 'n')
   at Microsoft.FSharp.Collections.PrivateListHelpers.nth[a](FSharpList`1 l, Int32 n) in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4093
   at FSI_0001.writeList(FSharpList`1 items, Int32 startIndex, Int32 endIndex) in /Users/eugene/Documents/fsharp/program.fsx:line 5
   at <StartupCode$FSI_0001>.$FSI_0001.main@() in /Users/eugene/Documents/fsharp/program.fsx:line 13
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
Остановлено из-за ошибки

Здесь мы видим, что программа ожидаемо завершилась ошибкой, тем не менее мы также видим, что блок finally отработал, а файловый поток был закрыт.

Хотя в примере выше ресурсы освобождаются, тем не мнее исключение не обрабатывается. И для его обработки вызов функции обернем в конструкцию try...with:

let writeList (items: string list) startIndex endIndex =
   let writer: System.IO.StreamWriter = new System.IO.StreamWriter("test.txt")
   try
      for i = startIndex to endIndex do
          writer.WriteLine(items[i])
      ()
   finally
      writer.Flush()
      printfn "Закрытие потока"
      writer.Close()

let people = ["Tom"; "Bob"; "Sam"]

try
    writeList people 0 3
with
    | _ -> printfn "Произошла ошибка..."; ()
printfn "Запись завершена"

Теперь программа не упадет и завершится как положено:

Закрытие потока
Произошла ошибка...
Запись завершена

Если блок try из конструкции try...finally возвращает некоторое значение, то внешний блок with, который обрабатывает исключение, должен возвращать значение этого же типа. В примере выше это значение unit (то есть фактически ничего не возвращается). Но это может быть и какое-то определенное значение:

let writeList (items: string list) startIndex endIndex =
   let writer: System.IO.StreamWriter = new System.IO.StreamWriter("test.txt")
   try
      for i = startIndex to endIndex do
          writer.WriteLine(items[i])
      Some(endIndex - startIndex + 1)   //возвращаем количество записанных элементов
   finally
      writer.Close()

let printResult result = 
  match result with
  | Some n -> printfn "Успешно записано %d элементов" n
  | _ -> printfn "Произошла ошибка..."


let people = ["Tom"; "Bob"; "Sam"]

let result1 = 
  try
      writeList people 0 2
  with
      | _ ->  None

let result2 = 
  try
      writeList people 0 3
  with
      | _ ->  None

printResult result1
printResult result2

Здесь в блоке try в try..finally возвращаем количество записанных элементов через Some. А при обработке ошибки в блоке with возвращаем None. А в функции printResult в завимости от значения результата - Some(n) или None выводим то или иное сообщение на консоль.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850