3 people like it.
Like the snippet!
Disposable Scopes provided by scope {...} computational workflow.
This snippet provides a builder for scope {...} computational workflow that returns a Scope object implementing System.IDisposable interface.
All "use" bindings inside scope {...} workflow are kept from being disposed immediately when computation leaves scope. Instead, they are disposed only when resulting Scope object is disposed. This makes possible to access disposable outside the scope while it remains usable. This can be achieved by via return statement of workflow that can return, for example, a closure that encapsulates disposable.
scope {...} return value is available through Value property of Scope object. Scopes may be nested through let! binding that means that nested scope will be disposed when the resulting scope is disposed.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:
246:
247:
248:
249:
250:
251:
252:
253:
254:
255:
256:
257:
258:
259:
260:
261:
|
open System
type Interlocked = System.Threading.Interlocked
type Scope<'a> (value:'a) =
let queue = Collections.Generic.Queue<IDisposable>()
let mutable closed = 0
let close () =
let mutable exnList = [] // where we collect errors while inner objects are being disposed
for i in queue do
try
i.Dispose()
with
exn -> exnList <- exn :: exnList
match exnList with
| [] -> () // no errors occurred
| [exn] -> raise exn
| _ -> raise (AggregateException exnList)
member _.Value = value
member internal _.InnerDisposables = queue
member _.Close () =
match Interlocked.CompareExchange(&closed,1,0) with
| 0 -> close () // will run at most once
| _ -> ()
interface IDisposable with
member this.Dispose () = this.Close()
module UnsafeInternals =
let getInnerDisposables (x:Scope<_>) = x.InnerDisposables
(*
may be useful to provide builder extensions.
Note that there is unique instance of Scope inside workflow that is created by
Return function. It is not directly accessible from inside workflow. Other builder functions maintain its mutable state.
And they all run eagerly in one thread right now. However, builder extension method may access that instance. Thus, if anyone wants to pass
Scope instance between threads he must make a byvalue copy of it.
*)
type ScopeBuilder () =
member _. Return x = new Scope<_>(x)
member _.Using (res ,body) =
try
let (x:Scope<_>) = body res
x.InnerDisposables.Enqueue res
x
with
| exn ->
try
res.Dispose()
with
| exn1 ->
raise (AggregateException [|exn ; exn1|])
reraise()
member this.Bind (scope, body) =
let outerbody (scope' : Scope<_>) = body scope'.Value
this.Using (scope,outerbody)
(*
thus, let! x = {expr}
-- is equivalent to --
use scope = {expr}
// outerbody starts here
let x = scope.Value
// body starts here
*)
member this.ReturnFrom (anotherscope:Scope<_>) =
this.Using(anotherscope,fun thatscope -> this.Return thatscope.Value)
(*
created current scope using value of another scope
bound another scope to current scope as disposable
*)
[<AutoOpen>]
module ScopeBuilderInstance =
let scope = ScopeBuilder ()
// example
open System.IO
let somescope =
scope {
let bytes = System.Text.UTF8Encoding().GetBytes "this
is
multiline
text"
use ms = new MemoryStream(bytes)
use sr = new StreamReader(ms)
return
{|
Next = sr.ReadLine
MemoryStream = ms
|}
}
somescope.Value.Next() // reads text line by line
somescope.Value.MemoryStream // view state of memory stream in fsharp interactive
somescope.Close() // try execute above commands now
// demonstrate what happens on error and also disposing order
// define 2 helper functions
let useful name =
{
new IDisposable with
member _.Dispose () = printfn "disposed %s" name
}
let useless name =
{
new IDisposable with
member _.Dispose () = failwithf "failed to dispose %s" name
}
// demo : order of disposing
scope {
use _ = useful "1"
use _ = useful "2"
use _ = useful "3"
return 4
} |> (fun s -> s.Close() ; s.Close((*closing twice takes no effect*)))
// composite scope
scope {
use _ = useful "1"
use _ = useful "2"
let! v =
scope {
use _ = useful "4"
use _ = useful "5"
return 10
}
use _ = useful "3"
return v + 5
} |> (fun s -> printfn "value is %i" s.Value ; s.Close())
let sc1 =
scope {
use _ = useful "4"
use _ = useful "5"
return 10
}
//sc1.Close() // try to close sc1 before executing following statements
scope {
use _ = useful "1"
use _ = useful "2"
return! sc1
} |> (fun s -> printfn "value is %i" s.Value ; s.Close())
let throwingscope =
scope {
use _ = useful "1"
use _ = useful "2"
use _ = useless "3"
use _ = useful "4"
use _ = useless "5"
return ()
}
// now we shall close it
try
throwingscope.Close()
with
| :? AggregateException as agexn ->
printfn "AggregateException\n%s" <| agexn.Flatten().Message
| exn ->
printfn "just an exception\n %s" exn.Message
// try execute the same code outside computation expression
try
use _ = useful "1"
use _ = useful "2"
use _ = useless "3"
use _ = useful "4"
use _ = useless "5" // exception is lost
()
with
| :? AggregateException as agexn ->
printfn "AggregateException\n%s" <| agexn.Flatten().Message
| exn ->
printfn "just an exception\n %s" exn.Message
// when exception occurs when creating scope
try
scope {
use _ = useful "1"
use _ = useful "2"
use _ = useless "3"
failwith "error!!!"
use _ = useful "4"
return ()
} |> ignore
with
| :? AggregateException as agexn ->
printfn "AggregateException\n%s" <| agexn.Flatten().Message
| exn ->
printfn "just an exception\n %s" exn.Message
(*---------some tricks we can use with scopes--------*)
// preparation 1 : helper type
// we'll use it to construct guarded methods
type Disposable (?name) =
let objectName = defaultArg name "disposable"
let mutable isDisposed = false
member _.Close() = isDisposed <- true
member _.IsDisposed = isDisposed
member _.Check() =
if isDisposed then
raise (ObjectDisposedException objectName)
interface IDisposable with
member this.Dispose () = this.Close()
// preparation 2 : helper function
// use IDisposable as pattern to autoclean filesystem of temporary files
module File =
// file at path will be deleted on dispose
let autoDelete path =
{
new IDisposable with
member _.Dispose() =
if File.Exists path then
File.Delete path
}
// example
let notebookScope =
scope {
use _ = File.autoDelete "tempfile" // tempfile willbe deleted on dispose
use guard = new Disposable "notebook"
return
{|
WriteLine =
fun line ->
guard.Check()
File.AppendAllLines ("tempfile",[|line|])
Print =
fun () ->
guard.Check()
File.ReadAllText "tempfile"
|> printfn "%s"
|}
}
let notebook = notebookScope.Value
notebook.WriteLine "Hello World!" // tempfile appears in your IDE explorer
notebook.WriteLine "Our experience with scopes"
notebook.Print()
notebookScope.Close() // tempfile disappeared
notebook.Print() // ObjectDisposedException thrown by guard
|
namespace System
type Interlocked = Threading.Interlocked
namespace System.Threading
type Interlocked =
static member Add : location1:int * value:int -> int + 1 overload
static member CompareExchange : location1:int * value:int * comparand:int -> int + 6 overloads
static member Decrement : location:int -> int + 1 overload
static member Exchange : location1:int * value:int -> int + 6 overloads
static member Increment : location:int -> int + 1 overload
static member MemoryBarrier : unit -> unit
static member MemoryBarrierProcessWide : unit -> unit
static member Read : location:int64 -> int64
Multiple items
type Scope<'a> =
interface IDisposable
new : value:'a -> Scope<'a>
member Close : unit -> unit
member internal InnerDisposables : Queue<IDisposable>
member Value : 'a
--------------------
new : value:'a -> Scope<'a>
val value : 'a
val queue : Collections.Generic.Queue<IDisposable>
Multiple items
namespace System.Collections
--------------------
namespace Microsoft.FSharp.Collections
namespace System.Collections.Generic
Multiple items
type Queue<'T> =
new : unit -> Queue<'T> + 2 overloads
member Clear : unit -> unit
member Contains : item:'T -> bool
member CopyTo : array:'T[] * arrayIndex:int -> unit
member Count : int
member Dequeue : unit -> 'T
member Enqueue : item:'T -> unit
member GetEnumerator : unit -> Enumerator<'T>
member Peek : unit -> 'T
member ToArray : unit -> 'T[]
...
nested type Enumerator
--------------------
Collections.Generic.Queue() : Collections.Generic.Queue<'T>
Collections.Generic.Queue(capacity: int) : Collections.Generic.Queue<'T>
Collections.Generic.Queue(collection: Collections.Generic.IEnumerable<'T>) : Collections.Generic.Queue<'T>
type IDisposable =
member Dispose : unit -> unit
val mutable closed : int
val close : (unit -> unit)
val mutable exnList : exn list
val i : IDisposable
Multiple items
val exn : exn
--------------------
type exn = Exception
val raise : exn:Exception -> 'T
Multiple items
type AggregateException =
inherit Exception
new : unit -> AggregateException + 6 overloads
member Flatten : unit -> AggregateException
member GetBaseException : unit -> Exception
member GetObjectData : info:SerializationInfo * context:StreamingContext -> unit
member Handle : predicate:Func<Exception, bool> -> unit
member InnerExceptions : ReadOnlyCollection<Exception>
member Message : string
member ToString : unit -> string
--------------------
AggregateException() : AggregateException
AggregateException(message: string) : AggregateException
AggregateException(innerExceptions: Collections.Generic.IEnumerable<exn>) : AggregateException
AggregateException([<ParamArray>] innerExceptions: exn []) : AggregateException
AggregateException(message: string, innerException: exn) : AggregateException
AggregateException(message: string, innerExceptions: Collections.Generic.IEnumerable<exn>) : AggregateException
AggregateException(message: string, [<ParamArray>] innerExceptions: exn []) : AggregateException
Threading.Interlocked.CompareExchange<'T (requires reference type)>(location1: byref<'T>, value: 'T, comparand: 'T) : 'T
Threading.Interlocked.CompareExchange(location1: byref<nativeint>, value: nativeint, comparand: nativeint) : nativeint
Threading.Interlocked.CompareExchange(location1: byref<obj>, value: obj, comparand: obj) : obj
Threading.Interlocked.CompareExchange(location1: byref<float>, value: float, comparand: float) : float
Threading.Interlocked.CompareExchange(location1: byref<float32>, value: float32, comparand: float32) : float32
Threading.Interlocked.CompareExchange(location1: byref<int64>, value: int64, comparand: int64) : int64
Threading.Interlocked.CompareExchange(location1: byref<int>, value: int, comparand: int) : int
val this : Scope<'a>
val getInnerDisposables : x:Scope<'a> -> Collections.Generic.Queue<IDisposable>
val x : Scope<'a>
Multiple items
type ScopeBuilder =
new : unit -> ScopeBuilder
member Bind : scope:Scope<'b> * body:('b -> Scope<'c>) -> Scope<'c>
member Return : x:'f -> Scope<'f>
member ReturnFrom : anotherscope:Scope<'a> -> Scope<'a>
member Using : res:'d * body:('d -> Scope<'e>) -> Scope<'e> (requires 'd :> IDisposable)
--------------------
new : unit -> ScopeBuilder
val x : 'f
val res : #IDisposable
val body : (#IDisposable -> Scope<'e>)
val x : Scope<'e>
val exn1 : exn
val reraise : unit -> 'T
val this : ScopeBuilder
val scope : Scope<'b>
val body : ('b -> Scope<'c>)
val outerbody : (Scope<'b> -> Scope<'c>)
val scope' : Scope<'b>
val anotherscope : Scope<'a>
val thatscope : Scope<'a>
Multiple items
type AutoOpenAttribute =
inherit Attribute
new : unit -> AutoOpenAttribute
new : path:string -> AutoOpenAttribute
member Path : string
--------------------
new : unit -> AutoOpenAttribute
new : path:string -> AutoOpenAttribute
val scope : ScopeBuilder
namespace System.IO
val somescope : Scope<{| MemoryStream: MemoryStream; Next: (unit -> string) |}>
val bytes : byte []
namespace System.Text
Multiple items
type UTF8Encoding =
inherit Encoding
new : unit -> UTF8Encoding + 2 overloads
member Equals : value:obj -> bool
member GetByteCount : chars:string -> int + 3 overloads
member GetBytes : chars:ReadOnlySpan<char> * bytes:Span<byte> -> int + 3 overloads
member GetCharCount : bytes:ReadOnlySpan<byte> -> int + 2 overloads
member GetChars : bytes:ReadOnlySpan<byte> * chars:Span<char> -> int + 2 overloads
member GetDecoder : unit -> Decoder
member GetEncoder : unit -> Encoder
member GetHashCode : unit -> int
member GetMaxByteCount : charCount:int -> int
...
--------------------
Text.UTF8Encoding() : Text.UTF8Encoding
Text.UTF8Encoding(encoderShouldEmitUTF8Identifier: bool) : Text.UTF8Encoding
Text.UTF8Encoding(encoderShouldEmitUTF8Identifier: bool, throwOnInvalidBytes: bool) : Text.UTF8Encoding
val ms : MemoryStream
Multiple items
type MemoryStream =
inherit Stream
new : unit -> MemoryStream + 6 overloads
member CanRead : bool
member CanSeek : bool
member CanWrite : bool
member Capacity : int with get, set
member CopyTo : destination:Stream * bufferSize:int -> unit
member CopyToAsync : destination:Stream * bufferSize:int * cancellationToken:CancellationToken -> Task
member Flush : unit -> unit
member FlushAsync : cancellationToken:CancellationToken -> Task
member GetBuffer : unit -> byte[]
...
--------------------
MemoryStream() : MemoryStream
MemoryStream(capacity: int) : MemoryStream
MemoryStream(buffer: byte []) : MemoryStream
MemoryStream(buffer: byte [], writable: bool) : MemoryStream
MemoryStream(buffer: byte [], index: int, count: int) : MemoryStream
MemoryStream(buffer: byte [], index: int, count: int, writable: bool) : MemoryStream
MemoryStream(buffer: byte [], index: int, count: int, writable: bool, publiclyVisible: bool) : MemoryStream
val sr : StreamReader
Multiple items
type StreamReader =
inherit TextReader
new : stream:Stream -> StreamReader + 10 overloads
member BaseStream : Stream
member Close : unit -> unit
member CurrentEncoding : Encoding
member DiscardBufferedData : unit -> unit
member EndOfStream : bool
member Peek : unit -> int
member Read : unit -> int + 2 overloads
member ReadAsync : buffer:Memory<char> * ?cancellationToken:CancellationToken -> ValueTask<int> + 1 overload
member ReadBlock : buffer:Span<char> -> int + 1 overload
...
--------------------
StreamReader(stream: Stream) : StreamReader
(+0 other overloads)
StreamReader(path: string) : StreamReader
(+0 other overloads)
StreamReader(stream: Stream, detectEncodingFromByteOrderMarks: bool) : StreamReader
(+0 other overloads)
StreamReader(stream: Stream, encoding: Text.Encoding) : StreamReader
(+0 other overloads)
StreamReader(path: string, detectEncodingFromByteOrderMarks: bool) : StreamReader
(+0 other overloads)
StreamReader(path: string, encoding: Text.Encoding) : StreamReader
(+0 other overloads)
StreamReader(stream: Stream, encoding: Text.Encoding, detectEncodingFromByteOrderMarks: bool) : StreamReader
(+0 other overloads)
StreamReader(path: string, encoding: Text.Encoding, detectEncodingFromByteOrderMarks: bool) : StreamReader
(+0 other overloads)
StreamReader(stream: Stream, encoding: Text.Encoding, detectEncodingFromByteOrderMarks: bool, bufferSize: int) : StreamReader
(+0 other overloads)
StreamReader(path: string, encoding: Text.Encoding, detectEncodingFromByteOrderMarks: bool, bufferSize: int) : StreamReader
(+0 other overloads)
val useful : name:string -> IDisposable
val name : string
val printfn : format:Printf.TextWriterFormat<'T> -> 'T
val useless : name:string -> IDisposable
val failwithf : format:Printf.StringFormat<'T,'Result> -> 'T
val s : Scope<int>
val v : int
val sc1 : Scope<int>
val throwingscope : Scope<unit>
val agexn : AggregateException
val failwith : message:string -> 'T
val ignore : value:'T -> unit
Multiple items
type Disposable =
interface IDisposable
new : ?name:string -> Disposable
member Check : unit -> unit
member Close : unit -> unit
member IsDisposed : bool
--------------------
new : ?name:string -> Disposable
val name : string option
val objectName : string
val defaultArg : arg:'T option -> defaultValue:'T -> 'T
val mutable isDisposed : bool
Multiple items
type ObjectDisposedException =
inherit InvalidOperationException
new : objectName:string -> ObjectDisposedException + 2 overloads
member GetObjectData : info:SerializationInfo * context:StreamingContext -> unit
member Message : string
member ObjectName : string
--------------------
ObjectDisposedException(objectName: string) : ObjectDisposedException
ObjectDisposedException(objectName: string, message: string) : ObjectDisposedException
ObjectDisposedException(message: string, innerException: exn) : ObjectDisposedException
val this : Disposable
type File =
static member AppendAllLines : path:string * contents:IEnumerable<string> -> unit + 1 overload
static member AppendAllLinesAsync : path:string * contents:IEnumerable<string> * ?cancellationToken:CancellationToken -> Task + 1 overload
static member AppendAllText : path:string * contents:string -> unit + 1 overload
static member AppendAllTextAsync : path:string * contents:string * ?cancellationToken:CancellationToken -> Task + 1 overload
static member AppendText : path:string -> StreamWriter
static member Copy : sourceFileName:string * destFileName:string -> unit + 1 overload
static member Create : path:string -> FileStream + 2 overloads
static member CreateText : path:string -> StreamWriter
static member Decrypt : path:string -> unit
static member Delete : path:string -> unit
...
val autoDelete : path:string -> IDisposable
val path : string
File.Exists(path: string) : bool
File.Delete(path: string) : unit
val notebookScope : Scope<{| Print: (unit -> unit); WriteLine: (string -> unit) |}>
val guard : Disposable
val line : string
File.AppendAllLines(path: string, contents: Collections.Generic.IEnumerable<string>) : unit
File.AppendAllLines(path: string, contents: Collections.Generic.IEnumerable<string>, encoding: Text.Encoding) : unit
File.ReadAllText(path: string) : string
File.ReadAllText(path: string, encoding: Text.Encoding) : string
val notebook : {| Print: (unit -> unit); WriteLine: (string -> unit) |}
More information