使用Task.Run和Parallel.For

时间:2018-08-20 15:02:31

标签: c# .net asynchronous xunit

我有两个最终都更新同一对象的服务,因此我们进行了一项测试,以确保对该对象的写入完成(实际上,我们对每个对象都有重试策略)。

十分之九的理论中,有一个或多个理论将失败,task.ShouldNotBeNull();始终是失败的断言。我在此示例中的异步代码有什么问题?为什么任务为空?

    [Theory]
    [InlineData(1)]
    [InlineData(5)]
    [InlineData(10)]
    [InlineData(20)]
    public async Task ConcurrencyIssueTest(int iterations)
    {
        var orderResult = await _driver.PlaceOrder();

        var tasksA = new List<Task<ApiResponse<string>>>();
        var tasksB = new List<Task<ApiResponse<string>>>();

        await Task.Run(() => Parallel.For(1, iterations,
            x =>
            {
                tasksA.Add(_Api.TaskA(orderResult.OrderId));
                tasksB.Add(_Api.TaskB(orderResult.OrderId));
            }));

        //Check all tasks return successful           
        foreach (var task in tasksA)
        {
            task.ShouldNotBeNull();

            var result = task.GetAwaiter().GetResult();

            result.ShouldNotBeNull();
            result.StatusCode.ShouldBe(HttpStatusCode.OK);
        }

         foreach (var task in tasksB)
        {
            task.ShouldNotBeNull();

            var result = task.GetAwaiter().GetResult();

            result.ShouldNotBeNull();
            result.StatusCode.ShouldBe(HttpStatusCode.OK);
        }

    }
}

2 个答案:

答案 0 :(得分:1)

这里不需要Tasks和Parrallel循环。我假设您的_api通话受到IO限制?您想要更多类似这样的东西:

var tasksA = new List<Task<ApiResponse<string>>>();
var tasksB = new List<Task<ApiResponse<string>>>();

//fire off all the async tasks
foreach(var it in iterations){
   tasksA.Add(_Api.TaskA(orderResult.OrderId));
   tasksB.Add(_Api.TaskB(orderResult.OrderId));
}

//await the results
await Task.WhenAll(tasksA).ConfigureAwait(false);

foreach (var task in tasksA)
{
    //no need to get GetAwaiter(), you've awaited above.
    task.Result;
} 

//to get the most out of the async only await them just before you need them
await Task.WhenAll(tasksB).ConfigureAwait(false);

foreach (var task2 in tasksB)
{
     task2.Result;
}

这将触发您所有的api调用async,然后在返回结果时阻止。您的并行和任务仅使用其他线程池线程即可获得零收益。

如果_api受CPU限制,您可以从Task.Run中受益,但是我猜想它们是Web api之类的东西。因此Task.Run除了使用其他线程外什么也没做。

答案 1 :(得分:1)

正如其他人所建议的那样,删除Parallelawait上的所有任务以在asserting之前完成。

我还建议从每个任务中删除.Result,并改为await

public async Task ConcurrencyIssueTest(int iterations)
{
    var orderResult = await _driver.PlaceOrder();

    var taskA = _Api.TaskA(orderResult.OrderId);
    var taskB = _Api.TaskB(orderResult.OrderId);

    await Task.WhenAll(taskA, taskB);

    var taskAResult = await taskA;

    taskAResult.ShouldNotBeNull();
    taskAResult.StatusCode.ShouldBe(HttpStatusCode.OK);

    var taskBResult = await taskB;

    taskBResult.ShouldNotBeNull();
    taskBResult.StatusCode.ShouldBe(HttpStatusCode.OK);
}