Created
March 4, 2017 17:29
-
-
Save lbargaoanu/c47dba44e0df965282c5efd048cfa4f6 to your computer and use it in GitHub Desktop.
A way to communicate between threads using events. Based on SynchronizationContext.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Specialized; | |
using System.ComponentModel; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Threading; | |
namespace Common | |
{ | |
// this is thread safe | |
// it avoids locking in OnFireEvent by using immutable data structures (the copy overhead is in Add | |
// and Remove) | |
// eventsList is not changed, it is copied and replaced with the updated version | |
// for each event, the handlers are kept in an array that is replaced on Add and Remove | |
public class EventsList | |
{ | |
private HybridDictionary eventsLists = new HybridDictionary(); | |
public void AddHandler<TEventArgs>(EventHandler<TEventArgs> handler) where TEventArgs : EventArgs | |
{ | |
lock(eventsLists) | |
{ | |
var handlers = GetHandlers<TEventArgs>(); | |
var runner = new EventRunner<TEventArgs>(handler); | |
Context context; | |
if(handlers == null) | |
{ | |
handlers = new[] { runner }; | |
context = Context.NewEvent; | |
} | |
else | |
{ | |
handlers = handlers.Concat(Enumerable.Repeat(runner, 1)).ToArray(); | |
context = Context.None; | |
} | |
SetNewHandlersList(handlers, context); | |
} | |
} | |
public void RemoveHandler<TEventArgs>(EventHandler<TEventArgs> handler) where TEventArgs : EventArgs | |
{ | |
lock(eventsLists) | |
{ | |
var handlers = GetHandlers<TEventArgs>(); | |
if(handlers == null) | |
{ | |
return; | |
} | |
handlers = handlers.Where(runner => runner.OriginalCallback != handler).ToArray(); | |
if(handlers.Length == 0) | |
{ | |
handlers = null; | |
} | |
SetNewHandlersList(handlers, Context.None); | |
} | |
} | |
public void OnFireEvent<TEventArgs>(TEventArgs eventArgs) where TEventArgs : EventArgs | |
{ | |
Debug.WriteLine("[OnFireEvent] " + typeof(TEventArgs)); | |
var handlers = GetHandlers<TEventArgs>(); | |
if(handlers == null) | |
{ | |
return; | |
} | |
foreach(var eventRunner in handlers) | |
{ | |
eventRunner.RunAsync(eventArgs); | |
} | |
} | |
private EventRunner<TEventArgs>[] GetHandlers<TEventArgs>() where TEventArgs : EventArgs | |
{ | |
return (EventRunner<TEventArgs>[]) eventsLists[typeof(TEventArgs)]; | |
} | |
private void SetNewHandlersList<TEventArgs>(EventRunner<TEventArgs>[] handlers, Context context) where TEventArgs : EventArgs | |
{ | |
var size = (context == Context.NewEvent) ? eventsLists.Count + 1 : eventsLists.Count; | |
var newDictionary = new HybridDictionary(size); | |
foreach(DictionaryEntry entry in eventsLists) | |
{ | |
newDictionary.Add(entry.Key, entry.Value); | |
} | |
newDictionary[typeof(TEventArgs)] = handlers; | |
eventsLists = newDictionary; | |
} | |
enum Context { None, NewEvent }; | |
private struct EventRunner<TEventArgs> where TEventArgs : EventArgs | |
{ | |
private readonly SynchronizationContext syncContext; | |
private readonly SendOrPostCallback callback; | |
internal readonly EventHandler<TEventArgs> OriginalCallback; | |
public EventRunner(EventHandler<TEventArgs> myEvent) | |
{ | |
syncContext = AsyncOperationManager.SynchronizationContext; | |
callback = state => myEvent(null, (TEventArgs) state); | |
OriginalCallback = myEvent; | |
} | |
public void RunAsync(TEventArgs e) | |
{ | |
syncContext.Post(callback, e); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment