在Visual Studio 2010中运行单元测试时,AutoResetEvent过早发出信号

本文关键字:AutoResetEvent 信号 Studio Visual 2010 单元测试 运行 | 更新日期: 2023-09-27 18:29:39

我正在VS2010中运行一个MS单元测试,以测试我的多线程应用程序。

应用程序使用AutoResetEvent来同步线程,声明如下:

private readonly AutoResetEvent captureParsedEvent = new AutoResetEvent(false);

主测试螺纹(ID:13)

主测试线程启动一个线程来解析捕获文件,然后在AutoResetEvent上调用WaitOne(),阻塞直到捕获完成:

int id = Thread.CurrentThread.ManagedThreadId;
CaptureManager.Instance.StartProcessingPackets();
Trace.WriteLine("[" + id + "]: WAITING ON CaptureParsedEvent");
captureParsedEvent.WaitOne();
Trace.WriteLine("[" + id + "]: WAITING ON CaptureParsedEvent DONE!");
// Analyse parsed capture...

旁注:代码最初在WaitOne()之后调用captureParsedEvent.Reset(),但我在研究此问题时删除了它,因为我的研究得出结论,这可能对AutoResetEvent对象没有必要。)

正在分析线程(ID:18)

同时,正在进行解析的线程向AutoResetEvent发出这样的信号:

private void InstanceManagerStateChanged(ManagerStateEventArgs ea, object sender)
{
    int id = Thread.CurrentThread.ManagedThreadId;
    switch(ea.CurrentState)
    {
        case ManagerState.ReadPacketsDone:
            Trace.WriteLine("'t[" + id + "]: CaptureParsedEvent SIGNAL");
            captureParsedEvent.Set();
            Trace.WriteLine("'t[" + id + "]: CaptureParsedEvent DONE!");
            break;
    }
}

通常情况下,一切都表现良好,我在输出窗口中看到以下预期输出:

[13]: WAITING ON CaptureParsedEvent
    [18]: CaptureParsedEvent SIGNAL
    [18]: CaptureParsedEvent DONE!
[13]: WAITING ON CaptureParsedEvent DONE!

然而,我间歇性地看到以下输出:

[13]: WAITING ON CaptureParsedEvent
[13]: WAITING ON CaptureParsedEvent DONE!

这显然给我带来了问题,因为捕获还没有真正完成解析。

上面的地方是captureParsedEvent.Set();唯一出现的地方,所以我知道没有其他人在发出该事件的信号。

几个问题:

  1. Trace.WriteLine()线程是否安全并按正确顺序输出跟踪?

  2. 我只在VS2010中运行单元测试时看到过这个问题——并行运行测试是否有什么有趣的事情,以及在这种情况下使用线程可能会导致问题?我的理解是测试是串行运行的,但不确定这是否正确。

在Visual Studio 2010中运行单元测试时,AutoResetEvent过早发出信号

您得到的似乎是由于在评估时ea.CurrentState不是ManagerState.ReadPacketsDone,所以它跳过了该case语句中的代码。ManualResetEvent没有设置自己,如果他们设置了,那么对每个人来说都是一个巨大的问题(我从来没有听说过其他人有这样的问题),所以你只需要确保没有其他人设置事件。

问题1:Trace.WriteLine()是线程安全的,但如果有多个线程调用写线,则不能保证这些调用会按顺序执行。但是,在您的情况下,SIGNALDONE消息将一个接一个地写入,因为它们是在同一个线程中执行的。更重要的是,如果您获得了正确的状态,那么至少CaptureParsedEvent SIGNAL将在WAITING ON CaptureParsedEvent DONE之前打印,因为它发生在您设置手动重置事件之前。发出信号后,不能保证WAITING ON CaptureParsedEvent DONECaptureParsedEvent DONE的打印顺序。

如果另一个线程同时在写,那么它可以在它们之间写一些东西。但正如我已经说过的:这很可能是由ea.CurrentState不是ManagerState.ReadPacketsDone引起的。

问题2:当你处理并发时,总是会有"有趣的事情"发生,或者它和你通常在并发编程中得到的一样"有趣":你只需要小心地线程。同样,我不认为您的问题来自并发,它只是看起来您没有处理正确的情况和/或其他人可以访问相同的ManualResetEvent

默认情况下,Visual Stufio 2010不会并行运行测试,您必须通过手动编辑测试设置文件(parallelExecutionCount=0)来启用此功能。

在查看您提供的代码时,罪魁祸首可能与信号是在singleton(CaptureManager.Istance)中完成的这一事实有关。可能存在以前的测试已经执行并且ManagerState已经完成的情况。试着自己运行测试来验证这个假设。

如果您按顺序运行测试,您可能需要在测试之间重置单例的状态,以避免类似的副作用。然而,请注意,在使用singleton的任何地方都必须这样做,如果在整个代码库中大量使用此类,这可能会被证明是不理想的。

如果您并行运行测试,那么所有的赌注都将落空,因为您无法保证在执行测试时处于已知状态。您最好的选择是重新设计对象之间的关系,以便在没有副作用的情况下实例化和执行图形。

我已经解决了我的问题。

事实证明,对于代码正在处理的每个数据包,它都将InstanceManagerStateChanged()回调添加到委托列表中:

CaptureManager.Instance.ManagerStateChanged += InstanceManagerStateChanged;

但我们没有正确取消订阅,这意味着我可能收到了前一个数据包的通知。

在处理下一个数据包之前取消订阅修复了此问题:

CaptureManager.Instance.ManagerStateChanged -= InstanceManagerStateChanged;