I'm working on an EventManager
that is responsible for registering some Subscribers
and notify them when an event is raised.
I want to simulate a loop scenario to find a way to prevent it.Here's the test that I've written :
[Test]
public void LoopTest()
{
var eventManager = new EventManager();
eventManager.IntroduceEvent("A",typeof(EventArgs));
eventManager.IntroduceEvent("B", typeof(EventArgs));
eventManager.Subscribe<EventArgs>("A", (sender, args) =>
{
Console.WriteLine("Raise B");
eventManager.RaiseAsync("B", sender, args);
});
eventManager.Subscribe<EventArgs>("B", (sender, args) =>
{
Console.WriteLine("Raise A");
eventManager.RaiseAsync("A", sender, args);
});
eventManager.RaiseAsync<EventArgs>("A",null,null).Wait();
}
and here's the async
method :
public Task RaiseAsync<T>(string eventName, object sender, T eventArgs)
{
EnsureEventIsIntroduced(eventName, typeof (T));
var tasks = new List<Task>();
_subscribers[eventName].Values.ToList()
.ForEach(
subscriber =>
tasks.Add(Task.Factory.StartNew(() => ((Action<object, T>) subscriber)(sender, eventArgs))));
return Task.WhenAll(tasks);
}
When I run this test using Resharper test runner I see the following result in output and test passes.
Raise B
Raise A
Raise B
Raise A
Raise B
Although, I expect that this test should produce an infinite loop . Would you please explain what's going on ? (However the sync
version of this test produces an infinite loop.)
There are three reasons you're not seeing an infinite loop.
Firstly, your test isn't waiting for the event to complete. You should change your test to:
[Test]
public async Task LoopTest()
{
...
await eventManager.RaiseAsync<EventArgs>("A", null, null);
}
Secondly, when you add a subscriber which raises the event again, that's not waiting for the event to complete either.
Thirdly, in your RaiseAsync
, you're only waiting for the tasks which start new tasks to complete. You're not waiting for the subscribers themselves to complete.
I'd strongly advise the use of a foreach
loop - or just Select
- in your RaiseAsync
method, by the way. It would be clearer as:
var tasks = _subscribers[eventName]
.Values
.Cast<Action<object, T>>()
.Select(subscriber => Task.Run(() => subscriber(sender, eventArgs)))
.ToList();
It's not entirely clear what you actually want to happen though, which makes it hard to provide proper working code. If you want asynchronous event handlers, they should probably be Func<object, T, Task>
rather than Action<object, T>
, which you'd subscribe to using async lambdas.
See more on this question at Stackoverflow