I think there's a "'white lie" in your question; I don't think that your async snippet can lose events unless one of: Process() fire events, Process() pumps messages, Process() is async and yields the GUI thread. (But I might be wrong.)
In any case, I can imagine wrapping an event stream in a queue so you never lose a message. Here was my attempt, that has undergone little testing and analysis, but I think might be right.
The NonBlockingEventQueue class takes an event, which it immediately subscribes to. Every event it hears goes in a queue. You can fetch elements via TryGetAsync, or unsubscribe via Dispose(). Everything (constructor, TryGetAsync, Dispose) must be called on the UI thread.
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
type NonBlockingEventQueue<'Del,'Args when 'Del : delegate<'Args,unit> and 'Del :> System.Delegate>(e:IEvent<'Del,'Args>) = let mutable q = new System.Collections.Generic.Queue<_>() let signal = new Event<unit>() let unsub = Observable.subscribe (fun x -> q.Enqueue(x); signal.Trigger()) e let mutable finished = false member this.TryGetAsync() = async { if q.Count <> 0 then let r = q.Dequeue() return Some(r) elif finished then return None else do! Async.AwaitEvent signal.Publish return! this.TryGetAsync() } interface System.IDisposable with member this.Dispose() = if not finished then finished <- true unsub.Dispose() open System.Windows.Forms [<System.STAThread>] do let form = new Form(Visible = true) let q = new NonBlockingEventQueue<_,_>(form.MouseMove) async { while true do let! mm = q.TryGetAsync() match mm with | Some(mm) -> printfn "%d %d" mm.X mm.Y | None -> printfn "no more" } |> Async.StartImmediate async { do! Async.Sleep(5000) (q :> System.IDisposable).Dispose() } |> Async.StartImmediate Application.Run(form)
On closer inspection, it looks to me like NonBlockingEventQueue in its current form doesn't actually solve anything. Systems.Collections.Generic.Queue isn't thread-safe, so the event "e" under observation must also fire only on the UI thread. Additionally it's still only listening for signal while Async.AwaitEvent is running, so even if you were using a thread-safe queue you'd sometimes miss an event's arrival and stay dormant (although when the next event arrived you'd wake up and notice both), which could lead to deadlock in some algorithms. Again this isn't a problem when all code runs on the UI thread, since there isn't anyone who would fire events while TryGetAsync is still processing--but in the general case (with caveats noted by Brian's post) you don't notice problems with missing events, period, when all code is running on the UI thread. So you can only use NonBlockingEventQueue in its current form in scenarios where it isn't really needed.
The first issue is simple to fix using standard techniques (SynchronizationContext or locks). The second issue requires using a signal which remains signalled until it gets noticed/handled: a ManualResetEvent is suitable. Here's one example implementation, which may have subtle concurrency bugs that I haven't noticed. It also has a wart in that it requires the SynchronizationContext to be passed in:
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
open System open System.Threading type CaptureEvents<'Delegate, 'Args> when 'Delegate :> Delegate and 'Delegate : delegate<'Args, unit> (event : IEvent<'Delegate,'Args>, ctx : SynchronizationContext) = let events = System.Collections.ObjectModel.Collection<'Args>() let moreEventsAvailable = new ManualResetEvent(false) let w = event.Subscribe(fun (args : _) -> ctx.Post((fun _ -> events.Add(args) moreEventsAvailable.Set() |> ignore) , null) ) member this.GetEvents() : Async<'Args list> = async { let! ready = Async.AwaitWaitHandle moreEventsAvailable let ret = ref [�] ctx.Send((fun _ -> ret := Seq.toList events events.Clear() moreEventsAvailable.Reset() |> ignore; ), null) return !ret } interface IDisposable with member this.Dispose() = do w.Dispose() moreEventsAvailable.Dispose() type FooEventArgs(n : int) = inherit EventArgs() member this.Num = n type FooHandler = delegate of obj * FooEventArgs -> unit type Foo() = let e = Event<FooHandler, FooEventArgs>() [�<CLIEvent>] member this.EventOfInterest = e.Publish member this.Provide i = e.Trigger(this, FooEventArgs(i)) let someObject = Foo() let ctx = SynchronizationContext.Current // capture the GUI thread Sync context async { use e = new CaptureEvents<_,_>(someObject.EventOfInterest, ctx) while true do let! events = e.GetEvents() for n in events do printfn "%d" n.Num } |> Async.Start for i in 1..10 do someObject.Provide i // Usually will only print "1"
Lossless event streams are tricky enough IMHO that it might make sense to an implementation in the standard library. For now I should probably stick to MailboxProcessor<'T> for my GUI control logic.
-Max
Ok, yeah, I assumed you were talking about GUI events. If you're talking about events from other threads, then this is a thornier problem, and MailboxProcessor would be my go-to solution.
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
let serializeEventsToGUI<'Del,'Args when 'Del : delegate<'Args,unit> and 'Del :> System.Delegate>(e:IEvent<'Del,'Args>) = let guiEv = new Event<'Args>() let ctxt = System.Threading.SynchronizationContext.Current let mbox = new MailboxProcessor<'Args>(fun inbox -> async { while true do let! msg = inbox.Receive() do! Async.SwitchToContext(ctxt) guiEv.Trigger(msg) }) mbox.Start() e.Add(fun args -> mbox.Post(args)) // note, does not unsubscribe guiEv.Publish open System.Windows.Forms [<System.STAThread>] do let form = new Form(Visible = true) // trigger crazy events from crazy threads let ev = new Event<int*int>() let safeGuiEvent = serializeEventsToGUI(ev.Publish) for t in 1..5 do async { do! Async.Sleep 100 for x in 1..5 do ev.Trigger(t,x) do! Async.Sleep 10 } |> Async.Start // pull all events in GUI, none are lost let count = ref 0 async { while true do let! r = Async.AwaitEvent safeGuiEvent incr count printfn "%A - got %d msgs so far" r !count } |> Async.StartImmediate Application.Run(form)
Hi Brian,
First off, thanks for the example. It is shorter than the version I came up with and I am still trying to digest the differences.
Second, you can lose events even if Process() is printfn, no async stuff required.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
open System type FooEventArgs(n : int) = inherit EventArgs() member this.Num = n type FooHandler = delegate of obj * FooEventArgs -> unit type Foo() = let e = Event<FooHandler,FooEventArgs>() [�<CLIEvent>] member this.EventOfInterest = e.Publish member this.Provide i = e.Trigger(this, FooEventArgs(i)) let someObject = Foo() async { while true do let! result = Async.AwaitEvent someObject.EventOfInterest printfn "%d" result.Num } |> Async.Start for i in 1..10 do someObject.Provide i // Usually will only print "1"
-Max
Topic tags
- f# × 3705
- websharper × 1897
- compiler × 286
- functional × 201
- ui next × 139
- c# × 121
- classes × 97
- web × 97
- .net × 84
- book × 84
- async × 76
- ui.next × 67
- bug × 54
- core × 49
- website × 49
- server × 45
- parallel × 43
- ui × 43
- enhancement × 41
- parsing × 41
- testing × 41
- trywebsharper × 41
- typescript × 37
- html × 35
- javascript × 35
- owin × 35
- asynchronous × 30
- monad × 28
- ocaml × 28
- tutorial × 27
- warp × 27
- haskell × 26
- sitelet × 25
- linq × 22
- workflows × 22
- wpf × 20
- fpish × 19
- introduction × 19
- silverlight × 19
- sitelets × 19
- monodevelop × 17
- rpc × 17
- suave × 17
- piglets × 16
- collections × 15
- feature request × 15
- jquery × 15
- templates × 15
- getting started × 14
- pipeline × 14
- kendoui × 13
- reactive × 12
- 4.1.0.171 × 11
- monads × 11
- opinion × 10
- 4.0.190.100-rc × 9
- deployment × 9
- fixed × 9
- formlets × 9
- in × 9
- json × 9
- plugin × 9
- proposal × 9
- scheme × 9
- solid × 9
- basics × 8
- concurrent × 8
- highcharts × 8
- how-to × 8
- python × 8
- 4.1.1.175 × 7
- complexity × 7
- documentation × 7
- visual studio × 7
- 4.1.2.178 × 6
- lisp × 6
- real-world × 6
- released in 4.0.192.103-rc × 6
- remoting × 6
- resources × 6
- scala × 6
- websharper ui.next × 6
- workshop × 6
- xaml × 6
- 4.0.193.110 × 5
- 4.2.3.236 × 5
- aspnetmvc × 5
- authentication × 5
- azure × 5
- bootstrap × 5
- conference × 5
- dsl × 5
- formlet × 5
- java × 5
- list × 5
- metaprogramming × 5
- ml × 5
- released in Zafir.4.0.188.91-beta10 × 5
- sql × 5
- visualstudio × 5
- websharper.forms × 5
- zafir × 5
- 4.0.192.106 × 4
- 4.0.195.127 × 4
- 4.1.0.38 × 4
- 4.2.1.86 × 4
- 4.2.6.118 × 4
- css × 4
- example × 4
- extensions × 4
- fsi × 4
- fsx × 4
- html5 × 4
- jqueryui × 4
- lift × 4
- reflection × 4
- remote × 4
- rest × 4
- spa × 4
- teaching × 4
- template × 4
- websocket × 4
- wontfix × 4
- 4.0.196.147 × 3
- 4.1.0.34 × 3
- 4.1.6.207 × 3
- 4.2.1.223-beta × 3
- 4.2.11.258 × 3
- 4.2.4.114 × 3
- 4.2.4.247 × 3
- 4.2.5.115 × 3
- 4.2.6.253 × 3
- 4.2.9.256 × 3
- ajax × 3
- alt.net × 3
- aml × 3
- asp.net mvc × 3
- canvas × 3
- cloudsharper × 3
- compilation × 3
- database × 3
- erlang × 3
- events × 3
- extension × 3
- file upload × 3
- forums × 3
- inline × 3
- issue × 3
- kendo × 3
- macro × 3
- mono × 3
- msbuild × 3
- mvc × 3
- pattern × 3
- piglet × 3
- released in Zafir.4.0.187.90-beta10 × 3
- svg × 3
- type provider × 3
- view × 3
- 4.1.1.64 × 2
- 4.1.5.203 × 2
- 4.1.7.232 × 2
- 4.2.10.257 × 2
- 4.2.3.111 × 2
- 4.2.5.249 × 2
- android × 2
- asp.net × 2
- beginner × 2
- blog × 2
- chart × 2
- client × 2
- client server app × 2
- clojure × 2
- computation expressions × 2
- constructor × 2
- corporate × 2
- courses × 2
- cufp × 2
- d3 × 2
- debugging × 2
- direct × 2
- discriminated union × 2
- docs × 2
- elm × 2
- endpoint × 2
- endpoints × 2
- enterprise × 2
- entity framework × 2
- event × 2
- f# interactive × 2
- fable × 2
- flowlet × 2
- formdata × 2
- forms × 2
- fsc × 2
- google maps × 2
- hosting × 2
- http × 2
- https × 2
- iis 8.0 × 2
- install × 2
- interactive × 2
- interface × 2
- iphone × 2
- iteratee × 2
- jobs × 2
- jquery mobile × 2
- keynote × 2
- lens × 2
- lenses × 2
- linux × 2
- listmodel × 2
- mac × 2
- numeric × 2
- oauth × 2
- obfuscation × 2
- offline × 2
- oop × 2
- osx × 2
- packaging × 2
- pattern matching × 2
- performance × 2
- pipelines × 2
- q&a × 2
- quotation × 2
- reference × 2
- released in Zafir.4.0.185.88-beta10 × 2
- rx × 2
- script × 2
- security × 2
- self host × 2
- seq × 2
- sockets × 2
- stm × 2
- tcp × 2
- trie × 2
- tutorials × 2
- type × 2
- url × 2
- var × 2
- websharper.charting × 2
- websharper4 × 2
- websockets × 2
- wig × 2
- xna × 2
- zh × 2
- .net interop × 1
- 2012 × 1
- 4.0.194.126 × 1
- 4.1.3.184 × 1
- 4.1.4.189 × 1
- 4.2.0.214-beta × 1
- 4.2.12.259 × 1
- 4.2.2.231-beta × 1
- 4.2.8.255 × 1
- Canvas Sample Example × 1
- DynamicStyle Animated Style × 1
- Fixed in 4.0.190.100-rc × 1
- Released in Zafir.UI.Next.4.0.169.79-beta10 × 1
- SvgDynamicAttribute × 1
- WebComponent × 1
- abstract class × 1
- accumulator × 1
- active pattern × 1
- actor × 1
- addin × 1
- agents × 1
- aggregation × 1
- agile × 1
- alter session × 1
- animation × 1
- anonymous object × 1
- apache × 1
- api × 1
- appcelerator × 1
- architecture × 1
- array × 1
- arrays × 1
- asp.net 4.5 × 1
- asp.net core × 1
- asp.net integration × 1
- asp.net mvc 4 × 1
- asp.net web api × 1
- aspnet × 1
- ast × 1
- attributes × 1
- authorization × 1
- b-tree × 1
- back button × 1
- badimageformatexception × 1
- bash script × 1
- batching × 1
- binding-vars × 1
- bistro × 1
- body × 1
- bundle × 1
- camtasia studio × 1
- cas protocol × 1
- charts × 1
- clarity × 1
- class × 1
- cli × 1
- clipboard × 1
- clojurescript × 1
- closures × 1
- cloud × 1
- cms × 1
- coding diacritics × 1
- color highlighting × 1
- color zones × 1
- combinator × 1
- combinators × 1
- compile × 1
- compile code on server × 1
- config × 1
- confirm × 1
- content × 1
- context × 1
- context.usersession × 1
- continuation-passing style × 1
- coords × 1
- cordova × 1
- cors × 1
- coursera × 1
- cross-domain × 1
- csla × 1
- current_schema × 1
- custom content × 1
- data × 1
- data grid × 1
- datetime × 1
- debug × 1
- declarative × 1
- delete × 1
- devexpress × 1
- dhtmlx × 1
- dictionary × 1
- directattribute × 1
- disqus × 1
- distance × 1
- do binding × 1
- doc elt ui.next upgrade × 1
- docker × 1
- dojo × 1
- dol × 1
- dom × 1
- domain × 1
- du × 1
- duf-101 × 1
- dynamic × 1
- eastern language × 1
- eclipse × 1
- edsl × 1
- em algorithm × 1
- emacs × 1
- emotion × 1
- enums × 1
- error × 1
- etw × 1
- euclidean × 1
- eventhandlerlist × 1
- examples × 1
- ext js × 1
- extension methods × 1
- extra × 1
- facet pattern × 1
- failed to translate × 1
- fake × 1
- fantomas × 1
- fear × 1
- float × 1
- form × 1
- form-data × 1
- forum × 1
- fp × 1
- frank × 1
- fsdoc × 1
- fsharp × 1
- fsharp.core × 1
- fsharp.powerpack × 1
- fsharpx × 1
- fsunit × 1
- function × 1
- functional style × 1
- game × 1
- games × 1
- gc × 1
- generic × 1
- geometry × 1
- getlastwin32error × 1
- getting-started × 1
- google × 1
- google.maps × 1
- grid × 1
- group × 1
- guide × 1
- hash × 1
- headers × 1
- hello world example × 1
- heroku × 1
- highchart × 1
- history × 1
- how to × 1
- html-templating × 1
- http405 × 1
- httpcontext × 1
- hubfs × 1
- i18n × 1
- ie 8 × 1
- if-doc × 1
- iis × 1
- image × 1
- images × 1
- inheritance × 1
- initialize × 1
- input × 1
- install "visual studio" × 1
- installer × 1
- int64 × 1
- interfaces × 1
- internet explorer × 1
- interop × 1
- interpreter × 1
- io × 1
- iobservable × 1
- ios × 1
- iot × 1
- ipad × 1
- isomorphic × 1
- javascript optimization × 1
- javascript semanticui resources × 1
- jquery-plugin × 1
- jquery-ui × 1
- jquery-ui-datepicker × 1
- js × 1
- kendo datasource × 1
- kendochart × 1
- kendoui compiler × 1
- knockout × 1
- l10n × 1
- learning × 1
- library × 1
- libs × 1
- license × 1
- licensing × 1
- lineserieszonescfg × 1
- local setting × 1
- localization × 1
- logging × 1
- loop × 1
- macros × 1
- mailboxprocessor × 1
- mapping × 1
- maps × 1
- markerclusterer × 1
- markup × 1
- marshal × 1
- math × 1
- mathjax × 1
- message × 1
- message passing × 1
- message-passing × 1
- meta × 1
- metro style × 1
- micro orm × 1
- minimum-requirements × 1
- mix × 1
- mobile installation × 1
- mod_mono × 1
- modal × 1
- module × 1
- mouseevent × 1
- mouseposition × 1
- multidimensional × 1
- multiline × 1
- multithreading × 1
- mysql × 1
- mysqlclient × 1
- nancy × 1
- native × 1
- nested × 1
- nested loops × 1
- node × 1
- nunit × 1
- object relation mapper × 1
- object-oriented × 1
- om × 1
- onboarding × 1
- onclick × 1
- optimization × 1
- option × 1
- orm × 1
- os x × 1
- output-path × 1
- override × 1
- paper × 1
- parameter × 1
- persistence × 1
- persistent data structure × 1
- phonegap × 1
- pola × 1
- post × 1
- powerpack × 1
- prefix tree × 1
- principle of least authority × 1
- privacy × 1
- private × 1
- profile × 1
- programming × 1
- project × 1
- project euler × 1
- projekt_feladat × 1
- protected × 1
- provider × 1
- proxy × 1
- ptvs × 1
- public × 1
- pure f# × 1
- purescript × 1
- qna × 1
- quant × 1
- query sitelet × 1
- question × 1
- quotations × 1
- range × 1
- raphael × 1
- razor × 1
- rc × 1
- reactjs × 1
- real-time × 1
- ref × 1
- region × 1
- released in 4.0.190.100-rc × 1
- reporting × 1
- responsive design × 1
- rest api × 1
- rest sitelet × 1
- restful × 1
- round table × 1
- router × 1
- routing × 1
- rpc reverseproxy × 1
- runtime × 1
- sales × 1
- sample × 1
- sampleapp × 1
- scriptcs × 1
- scripting × 1
- search × 1
- self hosted × 1
- semanticui × 1
- sequence × 1
- serialisation × 1
- service × 1
- session-state × 1
- sharepoint × 1
- signals × 1
- sitelet website × 1
- sitelet.protect × 1
- sitlets × 1
- slickgrid × 1
- source code × 1
- sqlentityconnection × 1
- ssl × 1
- standards × 1
- static content × 1
- stickynotes × 1
- streamreader × 1
- stress × 1
- strong name × 1
- structures × 1
- submitbutton × 1
- subscribe × 1
- svg example html5 websharper.ui.next × 1
- sweetalert × 1
- system.datetime × 1
- system.reflection.targetinvocationexception × 1
- table storage × 1
- targets × 1
- tdd × 1
- templates ui.next × 1
- templating × 1
- text parsing × 1
- three.js × 1
- time travel × 1
- tls × 1
- tooltip × 1
- tracing × 1
- tsunamiide × 1
- turkish × 1
- twitter-bootstrap × 1
- type erasure × 1
- type inference × 1
- type providers × 1
- type-providers × 1
- typeprovider × 1
- ui next forms × 1
- ui-next × 1
- ui.next jqueryui × 1
- ui.next charting × 1
- ui.next formlets × 1
- ui.next forms × 1
- ui.next suave visualstudio × 1
- ui.next templating × 1
- unicode × 1
- unittest client × 1
- upload × 1
- usersession × 1
- validation × 1
- vb × 1
- vb.net × 1
- vector × 1
- view.map × 1
- visal studio × 1
- visual f# × 1
- visual studio 11 × 1
- visual studio 2012 × 1
- visual studio shell × 1
- vs2017 compiler zafir × 1
- vsix × 1
- web api × 1
- web-scraping × 1
- webapi × 1
- webcomponents × 1
- webforms × 1
- webgl × 1
- webrtc × 1
- webshaper × 1
- websharper async × 1
- websharper codemirror × 1
- websharper f# google × 1
- websharper forms × 1
- websharper reactive × 1
- websharper rpc × 1
- websharper sitelets routing × 1
- websharper warp × 1
- websharper-interface-generator × 1
- websharper.chartsjs × 1
- websharper.com × 1
- websharper.exe × 1
- websharper.owin × 1
- websharper.ui.next × 1
- websharper.ui.next jquery × 1
- websockets iis × 1
- why-websharper × 1
- windows 7 × 1
- windows 8 × 1
- windows-phone × 1
- winrt × 1
- www.grabbitmedia.com × 1
- xamarin × 1
- xml × 1
- yeoman × 1
- yield × 1
- zafir beta × 1
- zafir websharper4 × 1
- zarovizsga × 1
![]() |
Copyright (c) 2011-2012 IntelliFactory. All rights reserved. Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy |
Built with WebSharper |
The problem domain is GUIs. I'm interested in decoupling GUI logic from GUI presentation objects by modeling each GUI component as an interface exposing one or more event streams. There's a stateful engine which is only aware of the interfaces (so it could be a test object instead of an actual GUI component), which keeps track of which interface is currently displayed and listens on it for the right events, and transitions to a different interface object when the right event comes along.
The problem: the event usage patterns I've seen before are lossy. They "lose" events from the stream nondeterministically.
Even though in this case we are always interested in the same event, we're not guaranteed that each triggering of EventOfInterest will result in a call to Process, since we only "hear" events which happen while we are waiting in the "let!" and not while we are executing Async.AwaitEvent or Process. There's a 1 to 0-1 correlation between events and calls to Process. I looked into using Tomas's AwaitObservable snippet instead but this doesn't seem to be the problem it's trying to solve.
I can solve this problem by instead subscribing to someObject.EventOfInterest with a callback which (in a threadsafe way) adds data to collection and sets an AutoResetEvent which I wait for in the let! via Async.AwaitWaithandle, but it seems there should be an easier way to capture the "whole" event stream between two points of time. Either
1.) My desire to capture the whole event stream is unusual, un-idiomatic and/or bad design.
2.) A standard library function or combinations of functions already abstracts this in a simple way. (What is it?)
3.) There is no standard library function which abstracts this, but I should write one and post it here. :)
3b.) It is impossible to abstract this pattern (never know until you try it.)
Which is it?
-Max