320 likes | 491 Views
F# Asynchronous and Reactive Programming Don Syme, Microsoft Research with help from Nikolaj Bjorner, Laurent le Brun, Luke Hoban, Brian McNamara, James Margetson. Today. A bit about F# Background: F# Monadic Syntax Asynchronous and Parallel Programming using F# workflows.
E N D
F# Asynchronous and Reactive Programming Don Syme, Microsoft Researchwith help from Nikolaj Bjorner, Laurent le Brun, Luke Hoban, Brian McNamara, James Margetson
Today • A bit about F# • Background: F# Monadic Syntax • Asynchronous and Parallel Programming using F# workflows
The .NET Context Visual Studio Debuggers, Profilers .NET Common Language Runtime C# XML Libraries GUI Libraries, etc. Visual Basic F# ML Graphics Libraries System.I/O System.Net etc. Database Connection Libraries
F# as a Language F# OCaml Core ML Core ML Similar core language Modules-as- values, functors “OCaml-Objects” and other extensions Other extensions .NET Nominal Objects + tools + tools
Pattern Matching • F# has five primary syntactic categories • Expressions • Declarations • Types • Patterns • Computation Expressions (= Workflows = Monadic Syntax)
F# “Workflow” Syntax • Monadic syntax added to F# aimed at: • List/Sequence/Array comprehensions • Database queries • Asynchronous/Reactive Programming • Probabilistic modelling • Modalities (client/server) • ≈≈ "monads", but rebranded, partly on advice seq { ... } async { ... } option { ... } seq<'a> = IEnumerable<'a> Async<'a> ≈ ('a -> unit) -> unit Optional<'a> = unit -> 'a option server { ... } client { ... } dist { ... } Server<'a> = unit -> 'a Client<'a> = unit -> 'a Distribution<'a>
F# “Workflow” Syntax async { let! image = readAsync "cat.jpg" let image2 = f image do! writeAsync image2 "dog.jpg" do printfn "done!" return image2 } Async<'a> { for x in 0 .. 10 -> (x,x*x) } [ for x in 0 .. 10 -> (x,x*x) ] seq<'a> = IEnumerable<'a> [| for x in 0 .. 10 -> (x,x*x) |] <@ { for c in db.Customers do for e in db.Employees do if c.Name = e.Name then yield (c.Name,c.Address,e.Address) } @> seq<'a> + meta-program member x.OnClick(EventArgs) = client { let! x = ... do! ... } member x.OnPageLoad() = server { ... } member OnClick : EventArgs -> Client<'a> member OnPageLoad : unit -> Server<'a>
F# “Workflow” Syntax Asynchronous "non-blocking" action async { let! image = readAsync "cat.jpg" let image2 = f image do! writeAsync image2 "dog.jpg" do printfn "done!" return image2 } Continuation/ Event callback You're actually writing this (approximately): async.Delay(fun () -> async.Bind(readAsync "cat.jpg", (fun image -> async.Bind(async.Return(f image),(fun image2 async.Bind(writeAsync "dog.jpg",(fun () -> async.Bind(async.Return(printfn "done!"),(fun () -> async.Return())))))))))
Full Workflow Syntax & Translation expr = ... | expr { cexpr } -- let v = expr in v.Delay(fun () -> «cexpr») | { cexpr } | [| cexpr |] | [ cexpr ] cexpr = let! pat = exprincexpr-- v.Bind(expr,(fun pat -> «cexpr»)) | let pat = exprincexpr-- v.Let(expr,(fun pat -> «cexpr»)) | use pat = exprincexpr-- v.Using(expr,(fun pat -> «cexpr»)) | use! pat = cexprincexpr-- v.BindUsing(expr,(fun pat -> «cexpr»)) | do! cexpr1 in cexpr2 -- «let! () = cexpr1 in cexpr2» | doexprincexpr -- «let () = cexpr1 in cexpr2» | for pat inexprdocexpr -- v.For(expr,(fun pat -> «cexpr»)) | whileexprdocexpr-- v.While((fun () -> expr), v.Delay(fun () -> «cexpr»)) | ifexprthencexprelsecexpr-- if expr then «cexpr1» else «cexpr2» | ifexprthencexpr-- if expr then «cexpr1» else v.Zero() | cexpr; cexpr-- v.Combine(«cexpr1», v.Delay(fun () -> «cexpr2»)) | ->expr-- v.Return(expr) | return expr-- v.Return(expr) | yield expr-- v.Yield(expr) | matchexprwith [pat ->cexpr]+ -- ... | trycexprfinallycexpr -- ... | trycexprwith pat ->cexpr -- ...
type MBuilder() with member For: #seq<'a> * ('a -> M<unit>) -> M<unit> member Zero : unit -> M<unit> member Sequence : M<unit> * M<'a> -> M<'a> member While : (unit -> bool) * M<unit> -> M<unit> member Return : 'a -> M<'a> member Delay : (unit -> M<'a>) -> M<'a> member Using: 'a * ('a -> M<'b>) -> M<'b> when 'a :> IDisposable member Let: 'a * ('a -> M<'b>) -> M<'b> member Bind: M<'a> * ('a -> M<'b>) -> M<'b> member TryFinally : M<'a> * (unit -> unit) -> M<'a> member TryWith : M<'a> * (exn -> M<'a>) -> M<'a> let async = AsyncBuilder() let seq = SeqBuilder() let attempt = AttemptBuilder()
F# “Workflow” Syntax • For "declarative control“, “modalities” and "queries“ • Not particularly for tracking effects, though can be used for this • Often combined with "separated" imperative programming // randomWalk : seq<float> letrandomWalk = { let state = ref 0.0 whiletruedo yield state.Value state := state.Value + rand() } // allLines : seq<string> letallLines(file) = { let inputStream = System.IO.File.OpenRead(file) while not inputStream.IsEndOfFiledo yield inputStream.ReadLine() } // dequeueAll : Queue<_> -> seq<string> letdequeueAll(queue:Queue<_>) = [ whilequeue.Count <> 0do yield queue.Dequeue() ]
F# Meta-programming SQL <@ { for c in db.Customersdo for e db.Employeesdo if c.Name = e.Name then yield (c.Name,c.Address,e.Address) } @> SQL : Expr<seq<'a>> -> seq<'a> Runtime intensional meta programming (= Typed Scheme Quotations) (= Expression Trees) SQLServer
Why is it so hard? To get 50 web pages in parallel? To get from thread to thread? To create a worker thread that reads messages? To handle failure on worker threads?
Why isn’t it this easy? let GetWebPages(urls) = Async.Run (Async.Parallel [ for i in urls-> GetWebPage(i) ])
Why isn’t it this easy? let ProcessImages() = Async.Run (Async.Parallel [ for i in 1 .. numImages -> ProcessImage(i) ])
Why isn’t it this easy? let task = async { ... do! SwitchToNewThread() ... do! SwitchToThreadPool() ... do! SwitchToGuiThread() .... }
Async<T> (“task generators”) • Return, Bind, TryCatch, TryFinally, Raise, Delay, Sequence etc. • Run, Spawn, SpawnFuture, SpawnChild • CancelCheck, WhenCancelled, CancelAsync • Parallel (= ForkJoin) • SwitchToNewThread, SwitchToThreadPool • BuildPrimitive
Taming Asynchronous I/O Target: make it easy to use Begin/End operations Stream.BeginRead : ... Stream.EndRead : IAsyncResult * ...
Stream.ReadAsync = BuildPrimitive(Stream.BeginRead,Stream.EndRead) • WebRequest.GetResponseAsync= BuildPrimitive(WebRequest.BeginRead,WebRequest.EndRead) • WaitHandle.WaitOneAsync(timeout) = BuildPrimitive(WaitHandle.BeginRead,WaitHandle.EndRead,timeout) • Database.GetDataAsync....
Taming Asynchronous I/O using System; using System.IO; using System.Threading; public class BulkImageProcAsync { public const String ImageBaseName = "tmpImage-"; public const intnumImages = 200; public const intnumPixels = 512 * 512; // ProcessImage has a simple O(N) loop, and you can vary the number // of times you repeat that loop to make the application more CPU- // bound or more IO-bound. public static intprocessImageRepeats = 20; // Threads must decrement NumImagesToFinish, and protect // their access to it through a mutex. public static intNumImagesToFinish = numImages; public static Object[] NumImagesMutex = new Object[0]; // WaitObject is signalled when all image processing is done. public static Object[] WaitObject = new Object[0]; public class ImageStateObject { public byte[] pixels; public intimageNum; public FileStreamfs; } public static void ReadInImageCallback(IAsyncResultasyncResult) { ImageStateObject state = (ImageStateObject)asyncResult.AsyncState; Stream stream = state.fs; intbytesRead = stream.EndRead(asyncResult); if (bytesRead != numPixels) throw new Exception(String.Format ("In ReadInImageCallback, got the wrong number of " + "bytes from the image: {0}.", bytesRead)); ProcessImage(state.pixels, state.imageNum); stream.Close(); // Now write out the image. // Using asynchronous I/O here appears not to be best practice. // It ends up swamping the threadpool, because the threadpool // threads are blocked on I/O requests that were just queued to // the threadpool. FileStreamfs = new FileStream(ImageBaseName + state.imageNum + ".done", FileMode.Create, FileAccess.Write, FileShare.None, 4096, false); fs.Write(state.pixels, 0, numPixels); fs.Close(); // This application model uses too much memory. // Releasing memory as soon as possible is a good idea, // especially global state. state.pixels = null; fs = null; // Record that an image is finished now. lock (NumImagesMutex) { NumImagesToFinish--; if (NumImagesToFinish == 0) { Monitor.Enter(WaitObject); Monitor.Pulse(WaitObject); Monitor.Exit(WaitObject); } } } public static void ProcessImagesInBulk() { Console.WriteLine("Processing images... "); long t0 = Environment.TickCount; NumImagesToFinish = numImages; AsyncCallbackreadImageCallback = new AsyncCallback(ReadInImageCallback); for (inti = 0; i < numImages; i++) { ImageStateObject state = new ImageStateObject(); state.pixels = new byte[numPixels]; state.imageNum = i; // Very large items are read only once, so you can make the // buffer on the FileStream very small to save memory. FileStreamfs = new FileStream(ImageBaseName + i + ".tmp", FileMode.Open, FileAccess.Read, FileShare.Read, 1, true); state.fs = fs; fs.BeginRead(state.pixels, 0, numPixels, readImageCallback, state); } // Determine whether all images are done being processed. // If not, block until all are finished. boolmustBlock = false; lock (NumImagesMutex) { if (NumImagesToFinish > 0) mustBlock = true; } if (mustBlock) { Console.WriteLine("All worker threads are queued. " + " Blocking until they complete. numLeft: {0}", NumImagesToFinish); Monitor.Enter(WaitObject); Monitor.Wait(WaitObject); Monitor.Exit(WaitObject); } long t1 = Environment.TickCount; Console.WriteLine("Total time processing images: {0}ms", (t1 - t0)); } } let ProcessImageAsync () = async{letinStream =File.OpenRead(sprintf"Image%d.tmp"i) let!pixels =inStream.ReadAsync(numPixels) letpixels' =TransformImage(pixels,i) letoutStream=File.OpenWrite(sprintf"Image%d.done"i) do!outStream.WriteAsync(pixels') doConsole.WriteLine"done!" } letProcessImagesAsyncWorkflow() = Async.Run (Async.Parallel [foriin1 .. numImages->ProcessImageAsynci ]) Processing 200 images in parallel
Taming Asynchronous I/O Open the file, synchronously Equivalent F# code (same perf) Read from the file, asynchronously let ProcessImageAsync(i) = async{letinStream =File.OpenRead(sprintf"source%d.jpg"i) let!pixels =inStream.ReadAsync(numPixels) letpixels' =TransformImage(pixels,i) letoutStream=File.OpenWrite(sprintf"result%d.jpg"i) do!outStream.WriteAsync(pixels') doConsole.WriteLine"done!" } letProcessImagesAsync() = Async.Run (Async.Parallel [foriin1 .. numImages->ProcessImageAsync(i) ]) This object coordinates Write the result, asynchronously Generate the tasks and queue them in parallel “!” = “asynchronous”
Taming Asynchronous I/O using System; using System.IO; using System.Threading; public class BulkImageProcAsync { public const String ImageBaseName = "tmpImage-"; public const int numImages = 200; public const int numPixels = 512 * 512; // ProcessImage has a simple O(N) loop, and you can vary the number // of times you repeat that loop to make the application more CPU- // bound or more IO-bound. public static int processImageRepeats = 20; // Threads must decrement NumImagesToFinish, and protect // their access to it through a mutex. public static int NumImagesToFinish = numImages; public static Object[] NumImagesMutex = new Object[0]; // WaitObject is signalled when all image processing is done. public static Object[] WaitObject = new Object[0]; public class ImageStateObject { public byte[] pixels; public int imageNum; public FileStream fs; } public static void ReadInImageCallback(IAsyncResultasyncResult) { ImageStateObject state = (ImageStateObject)asyncResult.AsyncState; Stream stream = state.fs; intbytesRead = stream.EndRead(asyncResult); if (bytesRead != numPixels) throw new Exception(String.Format ("In ReadInImageCallback, got the wrong number of " + "bytes from the image: {0}.", bytesRead)); ProcessImage(state.pixels, state.imageNum); stream.Close(); // Now write out the image. // Using asynchronous I/O here appears not to be best practice. // It ends up swamping the threadpool, because the threadpool // threads are blocked on I/O requests that were just queued to // the threadpool. FileStreamfs = new FileStream(ImageBaseName + state.imageNum + ".done", FileMode.Create, FileAccess.Write, FileShare.None, 4096, false); fs.Write(state.pixels, 0, numPixels); fs.Close(); // This application model uses too much memory. // Releasing memory as soon as possible is a good idea, // especially global state. state.pixels = null; fs = null; // Record that an image is finished now. lock (NumImagesMutex) { NumImagesToFinish--; if (NumImagesToFinish == 0) { Monitor.Enter(WaitObject); Monitor.Pulse(WaitObject); Monitor.Exit(WaitObject); } } } public static void ProcessImagesInBulk() { Console.WriteLine("Processing images... "); long t0 = Environment.TickCount; NumImagesToFinish = numImages; AsyncCallbackreadImageCallback = new AsyncCallback(ReadInImageCallback); for (inti = 0; i < numImages; i++) { ImageStateObject state = new ImageStateObject(); state.pixels = new byte[numPixels]; state.imageNum = i; // Very large items are read only once, so you can make the // buffer on the FileStream very small to save memory. FileStreamfs = new FileStream(ImageBaseName + i + ".tmp", FileMode.Open, FileAccess.Read, FileShare.Read, 1, true); state.fs = fs; fs.BeginRead(state.pixels, 0, numPixels, readImageCallback, state); } // Determine whether all images are done being processed. // If not, block until all are finished. boolmustBlock = false; lock (NumImagesMutex) { if (NumImagesToFinish > 0) mustBlock = true; } if (mustBlock) { Console.WriteLine("All worker threads are queued. " + " Blocking until they complete. numLeft: {0}", NumImagesToFinish); Monitor.Enter(WaitObject); Monitor.Wait(WaitObject); Monitor.Exit(WaitObject); } long t1 = Environment.TickCount; Console.WriteLine("Total time processing images: {0}ms", (t1 - t0)); } } Create 10, 000s of “asynchronous tasks” Mostly queued, suspended and executed in the thread pool let ProcessImageAsync () = async{letinStream =File.OpenRead(sprintf"Image%d.tmp"i) let!pixels =inStream.ReadAsync(numPixels) letpixels' =TransformImage(pixels,i) letoutStream=File.OpenWrite(sprintf"Image%d.done"i) do!outStream.WriteAsync(pixels') doConsole.WriteLine"done!" } letProcessImagesAsyncWorkflow() = Async.Run (Async.Parallel [foriin1 .. numImages->ProcessImageAsynci ]) Exceptions can be handled properly Cancellation checks inserted automatically Resources can be disposed properly on failure CPU threads are not blocked
Taming Asynchronous I/O letProcessImage(i) = async{use inStream =File.OpenRead(sprintf"source%d.jpg"i) let!pixels =inStream.ReadAsync(1024*1024) letpixels' =TransformImage(pixels,i) use outStream=File.OpenWrite(sprintf"result%d.jpg"i) do!outStream.WriteAsync(pixels') doConsole.WriteLine"done!" } letProcessImages() = Async.Run (Async.Parallel [foriin1 .. numImages->ProcessImage(i) ])
Taming Asynchronous I/O letProcessImage(i) = async{useinStream =File.OpenRead(sprintf"source%d.jpg"i) let!pixels =inStream.ReadAsync(1024*1024) letpixels' =TransformImage(pixels,i) useoutStream=File.OpenWrite(sprintf"result%d.jpg"i) do!outStream.WriteAsync(pixels') doConsole.WriteLine"done!" } letProcessImages() = Async.Run (Async.Parallel [foriin1 .. numImages->ProcessImage(i) ])
F# - Erlang-style Message Agents openMicrosoft.FSharp.Control.Mailboxes let counter = new MailboxProcessor<_>(fun inbox -> let rec loop(n) = async { doprintfn "n = %d" n let!msg = inbox.Receive() return! loop(n+msg) } loop(0)) Reactive State Machine
Show me the types Success continuation Async<T> Execution request Exception continuation type Async<'a> = P of { ctxt: AsyncGroup; cont: 'a -> unit; econt: exn -> unit; ccont: OperationCanceledException -> unit; } -> unit
Show me the types Success continuation Async<T> Execution request Exception continuation type Async a = P of { ctxt: AsyncGroup; cont: a -> IO (); econt: exn -> IO (); ccont: OperationCanceledException -> IO (); } -> IO ()
Microsoft Research F# 1.9.4 just released • Late Summer: “Community Technology Preview” • Thereafter moving to be part of standard Microsoft .NET Tools
Books about F# Visit http://research.microsoft.com/fsharp http://blogs.msdn.com/dsyme