C# 单元测试不';t似乎不允许parallel.foreach并行执行

C# 单元测试不';t似乎不允许parallel.foreach并行执行,c#,multithreading,unit-testing,parallel-processing,xunit.net,C#,Multithreading,Unit Testing,Parallel Processing,Xunit.net,我有一个C#单元测试(实际上更像是一个集成测试),它测试一个包含Parallel.ForEach循环的代码块。当此代码在调试模式下、本地或部署的环境中运行时,并行性将按预期工作(大约20秒后完成)。然而,当我的集成测试碰到那段代码时,它们最终会超时(耗时超过4分钟)。我的猜测是,当单元测试运行时,并行性不知何故被关闭了。我知道这不是硬件情况,因为同一台机器在调试时可以执行并行,而在运行单元测试时速度较慢。然而,我不知道单元测试的什么设置或配置会导致这种情况 请注意,这不是并行运行测试的问题-由于

我有一个C#单元测试(实际上更像是一个集成测试),它测试一个包含
Parallel.ForEach
循环的代码块。当此代码在调试模式下、本地或部署的环境中运行时,并行性将按预期工作(大约20秒后完成)。然而,当我的集成测试碰到那段代码时,它们最终会超时(耗时超过4分钟)。我的猜测是,当单元测试运行时,并行性不知何故被关闭了。我知道这不是硬件情况,因为同一台机器在调试时可以执行并行,而在运行单元测试时速度较慢。然而,我不知道单元测试的什么设置或配置会导致这种情况

请注意,这不是并行运行测试的问题-由于其他限制,我们必须以串行方式运行测试。这是在单个测试中,
并行的性能。如果在调试(通过IIS localhost)或运行单元测试时运行相同的代码块,则在同一台机器上的ForEach
的性能会有很大的不同

我们的单元测试框架是xUnit。我们使用TypeMock对静态调用进行大量模拟,但是我们没有配置任何应该禁止线程的东西。单元测试显示池中有32K+个线程可用。我可以有一个单独的单元测试,显示它可以并行运行

单元测试

[Fact]
public void AssertManyAssetsEnumerated()
{
    var args = new EnumerateArgs();
    args.FirstIndex = 0;
    args.LastIndex = 22263;
    args.FileName = LARGE_INDEX_ASSET_PREFIX;
    args.FullLibrarySearch = true;

    var stopwatch = new Stopwatch();
    var translators = new FilenameFieldTranslator();

    stopwatch.Restart();
    var assets = Services.AssetService.Enumerate(ScenarioBuilder.MasterAdminUser, args, translators);
    stopwatch.Stop();

    Assert.Equal(22263, assets.Count);

    var assetIDs = assets.Select(a => a.ID);
    Assert.Equal(assetIDs.Distinct(), assetIDs);

    var assetFileNames = assets.Select(a => a.FileName);
    Assert.Equal(assetFileNames.Distinct(), assetFileNames);

    Assert.Equal(PrependZerosAndCreateFileName(lower + 1), assets[0].FileName);
    Assert.Equal(PrependZerosAndCreateFileName(realUpper), assets.Last().FileName);

    //All asserts up to this point pass, only this time assertion fails
    var start = new TimeSpan(0, 0, 0);
    var maxTimespan = new TimeSpan(0, 0, 120);
    Assert.InRange(stopwatch.Elapsed, start, maxTimespan);
}
循环

public SearchResult RunSearch(IUser user, AssetCollection collection, Aggregations aggregations, FieldTranslators translators)
{
    if (!collection.IsLargeRequest)
    {
        return RunSingleSearch(user, collection, aggregations, translators);
    }

    var subquerySize = 1000;

    var subqueryCollections = new List<AssetCollection>();
    for (var i = collection.FirstIndex; i < collection.LastIndex; i += subquerySize)
    {
        var subqueryCollection = Clone(collection);
        subqueryCollection.FirstIndex = i;
        subqueryCollection.LastIndex = i + subquerySize;

        subqueryCollections.Add(subqueryCollection);
    }

    var results = new List<SearchResult>();
    var resultLock = new object();

    Parallel.ForEach(subqueryCollections, c =>
    {
        var result = RunSingleSearch(user, c, aggregations, translators);

        lock (resultLock)
        {
            results.Add(result);
        }
    });

    var overallResult = CombineResults(collection, results);

    return overallResult;
}
public SearchResult RunSearch(IUser用户、AssetCollection集合、聚合、字段转换器)
{
如果(!collection.IsLargeRequest)
{
返回RunSingleSearch(用户、集合、聚合、转换器);
}
var subquerySize=1000;
var subqueryCollections=新列表();
for(var i=collection.FirstIndex;i
{
var result=RunSingleSearch(用户、c、聚合、转换器);
锁(结果锁)
{
结果。添加(结果);
}
});
var overallResult=组合结果(集合、结果);
返回总体结果;
}
同样,在同一台机器上,我可以在web应用程序中运行此代码,并在20秒内执行该请求。在同一台机器上,用相同的代码在单元测试中执行相同的请求,需要4分钟。我已经陷入了很多死胡同关于什么可能会使它变慢,但我只是不知道

  • 我们在xUnit测试套件中禁用了并行执行,因为TypeMock隔离器不喜欢它(至少我们使用它的方式)。我尝试再次启用它,但它没有解决单元测试中的性能问题
  • 如前所述,我们使用TypeMock隔离器来模拟某些静态调用。我们没有做任何(我看到的)会限制/改变线程和/或任务的并行执行方式的事情
  • 单元测试中的线程池有32K+个可用线程
  • 单独的单元测试可以确认不同的
    并行。ForEach
    循环确实可以并行执行(在我一直使用的机器上,一次大约有10个线程)
  • 我们正在Visual Studio 2017中运行单元测试,使用最新版本的xUnit Visual Studio runner(2.4.0)

任何需要调查的帮助或建议都将不胜感激。

我建议使用xUnit编写单元测试,并且只编写单元测试,而不要使用xUnit编写集成测试。将集成测试与单元测试混合在一起不会有好的结果。它们应该分开存放。集成测试和单元测试有非常不同的特点,例如:集成测试几乎总是只能串行运行,单元测试应该总是能够并行运行。@Eljay我当然同意。然而,这是一个遗留项目,有许多缺点。我们正在努力在我们的解决方案中使用真正的单元测试,并将它们与集成分离开来——但目前,这不是一个选项。@ODawgG——由于列表本身不是线程安全的,锁确保所有集合都正确地添加到列表中。例如,如果我的请求大小为1M,并且它必须进行1000个子查询,那么有时会有1到2个结果由于线程冲突而无法添加。