You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ManualResetEventSlim在异步并发测试中的作用及测试改造方法

关于并发测试中ManualResetEventSlim的作用分析

咱们先来看你写的这段并发测试代码,核心目标是验证异步方法在多线程调用时,每个线程能拿到隔离的结果,不会互相干扰。接下来咱们逐一拆解你的问题:

一、原测试里ManualResetEventSlim到底起啥作用?

你在task1和task2里用的rs1、rs2这两个ManualResetEventSlim,本质是给两个异步任务“定规矩”,强制它们按你想要的顺序执行,精准控制expectedResult的修改时机:

  • rs2.Wait()让task2必须等task1完成expectedResult = 40的赋值、调用完sut.MyMethod()之后,才允许修改自己的expectedResult = 45
  • rs1.Wait()让task1必须等task2完成sut.MyMethod()和自身断言后,才执行自己的断言
  • 这么一来,task1用的是40这个值,task2用的是45这个值,两者的变量修改和方法调用完全错开,不会出现“task2提前改了值,导致task1断言出错”的情况(当然现在你的代码可能因为线程调度巧合,不用也能过,但这纯属运气)

二、用和不用ManualResetEventSlim的区别

用了的情况:

  • 线程执行顺序是完全可控的:task1先改值→调用方法→通知task2启动→task2改值→调用方法→通知task1断言→最后完成所有校验
  • 测试结果是稳定可靠的,不管操作系统怎么调度线程,都能保证每个线程用的是自己设置的expectedResult,不会出现偶发的断言失败

不用的情况:

  • 线程执行顺序是随机无序的,全看操作系统的线程调度:比如可能task2先把expectedResult改成45,task1才开始调用MyMethod,这时候task1的断言就会拿45去对比,而不是预期的40;但也可能刚好调度顺序符合你的期望,所以测试偶尔能过
  • 测试结果是不稳定的,属于“靠运气通过”,本质是测试逻辑有漏洞,没有真正模拟出并发冲突的场景

三、怎么改造测试,让不用ManualResetEventSlim就过不了?

核心思路是放大线程竞争的概率,让expectedResult的修改和MyMethod的调用更容易出现交叉冲突,具体可以这么改:

  1. 把Mock里的Thread.Sleep(10)改成更长的时间,比如Thread.Sleep(100),让MyMethod内部的异步操作耗时更久,线程调度的不确定性更高
  2. 增加更多并发任务,比如5个甚至10个,每个任务设置不同的expectedResult值
  3. 去掉ManualResetEventSlim的控制,让所有任务同时启动,并且启动后立刻修改expectedResult

改造后的示例代码:

public async Task MyMethod_Concurrency_ReturnsIsolatedResultPerThread()
{
    int expectedResult = 20;
    var theMock = new Mock<IService>();
    theMock.Setup(m => m.GetResult(It.IsAny<int>()))
           .Callback(() => Thread.Sleep(100)) // 延长耗时,放大线程竞争概率
           .Returns<int>(t => Task.FromResult(expectedResult));

    var sut = new MyClass(30, theMock.Object);
    var tasks = new List<Task>();

    // 创建多个并发任务,每个任务设置不同的目标结果值
    for (int i = 0; i < 5; i++)
    {
        int targetValue = 30 + i * 5;
        tasks.Add(Task.Run(async () =>
        {
            expectedResult = targetValue;
            await sut.MyMethod();
            Assert.AreEqual(targetValue, sut.Result);
        }));
    }

    tasks.Add(Task.Run(() => Assert.AreEqual(0, sut.Amount)));

    await Task.WhenAll(tasks);
}

这个时候,如果你的MyClass.MyMethod没有做到真正的线程隔离,那么去掉ManualResetEventSlim后,多个任务会同时修改expectedResult,导致断言失败——因为某个任务调用MyMethod时,expectedResult已经被另一个任务改成别的值了,而如果MyMethod没有在调用瞬间捕获当前的expectedResult值,就会出现结果不一致的情况。

内容的提问来源于stack exchange,提问作者user5032790

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:41:56