I work a lot with asynchronous workflows and agents in F #. While I delved into events, I noticed that the type Event <_> () is not thread safe.
Here I am not talking about the general problem of raising an event. I am actually talking about subscribing and deleting / removing from an event. For testing, I wrote this short program:
let event = Event<int>() let sub = event.Publish [<EntryPoint>] let main argv = let subscribe sub x = async { let mutable disposables = [] for i=0 to x do let dis = Observable.subscribe (fun x -> printf "%d" x) sub disposables <- dis :: disposables for dis in disposables do dis.Dispose() } Async.RunSynchronously(async{ let! x = Async.StartChild (subscribe sub 1000) let! y = Async.StartChild (subscribe sub 1000) do! x do! y event.Trigger 1 do! Async.Sleep 2000 }) 0
The program is simple. I create an event and a function that subscribes to a certain number of events, and after that I place each handler. I use another asynchronous calculation to spawn two instances of this function with Async.StartChild. After completing both functions, I fire the event to see if there are any other handlers left.
But when event.Trigger(1) as a result, there are still several handlers registered to the event. Somehow "1" will be printed on the console. Typically, this means that subscription and / or deletion are not thread safe.
And this is what I did not expect. If subscription and deletion are not thread safe, how can events be safely used?
Of course, events can also be used outside of threads, and the trigger does not generate any functions in parallel or in different threads. But for me it’s somehow normal that events are used in Async , agent code, or with threads in general. They are often used to exchange information about Backroundworker streams.
With Async.AwaitEvent, you can subscribe to an event. If subscription and deletion are not thread safe, how can events be used in such an environment? What purpose does Async.AwaitEvent have? Whereas an asynchronous workflow is executing a thread, hoping that a simple use of Async.AwaitEvent is mostly “design-disrupted” if subscribing / deleting an event is not thread safe by default.
The general question I am facing is: is it right that subscription and deletion are not thread safe? From my example, it looks like this, but maybe I missed some important details. Currently, I often use events in my design, and I usually have MailboxProcessors and use events for notifications. So the question is. If the events are not thread safe, then the entire project that I am currently using is not thread safe at all. So what to fix in this situation? Creating a whole new thread-oriented implementation of events? Are there already some implementations that face this problem? Or are there other options for using events safely in a multi-threaded environment?