我无法理解这种异步场景的行为

时间:2014-07-03 08:21:26

标签: c# task-parallel-library

我正在处理EventManager,负责注册一些Subscribers并在发生事件时通知他们。 我想模拟一个循环场景,找到一种方法来阻止它。这是我写的测试:

[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();
}

这是async方法:

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);
}

当我使用Resharper测试运行器运行此测试时,我在输出和测试通道中看到以下结果。

Raise B
Raise A
Raise B
Raise A
Raise B 

虽然,我希望这个测试应该产生一个无限循环。你能解释一下发生了什么吗? (但是,此测试的sync版本会产生无限循环。)

2 个答案:

答案 0 :(得分:2)

有三个原因你没有看到无限循环。

首先,您的测试不等待事件完成。您应该将测试更改为:

[Test]
public async Task LoopTest()
{
    ...
    await eventManager.RaiseAsync<EventArgs>("A", null, null);
}

其次,当您添加再次引发事件的订阅者时, 不等待事件完成。

第三,在RaiseAsync中,您只等待开始新任务完成的任务。您并没有等待订阅者自己完成。

顺便说一下,我强烈建议您在foreach方法中使用Select循环 - 或仅RaiseAsync - 。它会更清楚:

var tasks = _subscribers[eventName]
    .Values
    .Cast<Action<object, T>>()
    .Select(subscriber => Task.Run(() => subscriber(sender, eventArgs)))
    .ToList();

虽然不太清楚你实际想要发生什么,这使得很难提供正确的工作代码。如果您想要异步事件处理程序,它们应该是Func<object, T, Task>而不是Action<object, T>,您使用异步lambdas订阅它们。

答案 1 :(得分:2)

调用eventManager.RaiseAsync<EventArgs>("A",null,null);之后,任何后续的RaiseAsync调用都将在eventhandler中发生 - 并触发另一个异步任务。

然而,您的FIRST任务现在可以完成,因此您的调用将返回并且方法结束。发生这种情况时,会触发另外4个事件。

如果您在第一次通话后添加睡眠声明,您应该会看到更多后续异步事件处理过的方式:

[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 a little bit to get more async events processed
    System.Threading.Thread.Sleep(1000);
}

现在,主线程将被冻结1000ms,允许独立的异步子任务提升并处理更多事件。

在现场环境中,这将是一个无限循环。在这里,我假设测试环境在TestMethod结束后杀死所有其他异步任务很简单。