WaitAll之后再次触发任务

ZZZ*_*ZZZ 0 c# linq parallel-processing asynchronous task

HttpClient.GetAsync在Linq中使用或者任何异步方法或任何BCL异步方法Select可能会导致一些奇怪的两次射击.

这是一个单元测试用例:

[TestMethod]
public void TestTwiceShoot()
{
    List<string> items = new List<string>();
    items.Add("1");
    int k = 0;

    var tasks = items.Select(d =>
    {
        k++;
        var client = new System.Net.Http.HttpClient();
        return client.GetAsync(new Uri("http://testdevserver.ibs.local:8020/prestashop/api/products/1"));
    });

    Task.WaitAll(tasks.ToArray());

    foreach (var r in tasks)
    {

    }

    Assert.AreEqual(1, k);           
}
Run Code Online (Sandbox Code Playgroud)

测试将失败,因为k为2.不知何故,程序运行GetAsync两次触发的委托.为什么?

如果我删除foreach (var r in tasks),测试通过.为什么?

[TestMethod]
public void TestTwiceShoot()
{
    List<string> items = new List<string>();
    items.Add("1");
    int k = 0;

    var tasks = items.Select(d =>
    {
        k++;
        var client = new System.Net.Http.HttpClient();
        return client.GetAsync(new Uri("http://testdevserver.ibs.local:8020/prestashop/api/products/1"));
    });

    Task.WaitAll(tasks.ToArray());

    Assert.AreEqual(1, k);

}
Run Code Online (Sandbox Code Playgroud)

如果我使用foreach而不是items.Select,测试通过.为什么?

[TestMethod]
public void TestTwiceShoot()
{
    List<string> items = new List<string>();
    items.Add("1");
    int k = 0;

    var tasks = new List<Task<System.Net.Http.HttpResponseMessage>>();
    foreach (var item in items)
    {
        k++;
        var client = new System.Net.Http.HttpClient();
        tasks.Add( client.GetAsync(new Uri("http://testdevserver.ibs.local:8020/prestashop/api/products/1")));
    };

    Task.WaitAll(tasks.ToArray());

    foreach (var r in tasks)
    {

    }

    Assert.AreEqual(1, k);

}
Run Code Online (Sandbox Code Playgroud)

显然,返回的调查员items.SelectTask返回的对象生活不太好,一旦我走向调查员,代表就会再次被解雇.

这个测试通过.

[TestMethod]
public void TestTwiceShoot()
{
    List<string> items = new List<string>();
    items.Add("1");
    int k = 0;

    var tasks = items.Select(d =>
    {
        k++;
        var client = new System.Net.Http.HttpClient();
        return client.GetAsync(new Uri("http://testdevserver.ibs.local:8020/prestashop/api/products/1"));

    });


    var tasksArray = tasks.ToArray();
    Task.WaitAll(tasksArray);

    foreach (var r in tasksArray)
    {

    }

    Assert.AreEqual(1, k);

}
Run Code Online (Sandbox Code Playgroud)

斯科特提到,Select当走路时,可能会再次运行调查员,但这次测试通过了

[TestMethod]
public void TestTwiceShoot()
{
    List<string> items = new List<string>();
    items.Add("1");
    int k = 0;

    var tasks = items.Select(d =>
    {
        k++;
        return int.Parse(d);

    });

    foreach (var r in tasks)
    {

    };

    Assert.AreEqual(1, k);

}
Run Code Online (Sandbox Code Playgroud)

我猜Linq Select有一些特殊的对待Task.

毕竟,在Linq中触发多个异步方法的好方法是WaitAll什么?之后检查结果?

Sco*_*ain 5

这是因为tasksIEnumerable<Task>和每次在列表中列举的时间将重新运行.Select()操作.目前,您在列表中运行两次,一次是在您打电话时.ToArray(),一次是在您将其传递给foreach

要解决问题,只需使用.ToArray()您喜欢的,但要提前移动它.

    var tasks = items.Select(d =>
    {
        k++;
        var client = new System.Net.Http.HttpClient();
        return client.GetAsync(new Uri("http://testdevserver.ibs.local:8020/prestashop/api/products/1"));

    }).ToArray(); //This makes tasks a "Task[]" instead of a IEnumerable<Task>.

    Task.WaitAll(tasks);

    foreach (var r in tasks)
    {

    };
Run Code Online (Sandbox Code Playgroud)

发生在你身上的事情就是为什么微软推荐当你写Linq语句时他们没有任何副作用(比如递增k),因为很难说这句话会被运行多少次,特别是如果结果IEnumerable<T>出来的话.通过作为结果返回或传递给新函数的控制范围.