I Can't understand the behavior of this async scenario

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.)

Jon Skeet
people
quotationmark

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.

people

See more on this question at Stackoverflow