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.Select
与Task
返回的对象生活不太好,一旦我走向调查员,代表就会再次被解雇.
这个测试通过.
[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
什么?之后检查结果?
这是因为tasks
是IEnumerable<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>
出来的话.通过作为结果返回或传递给新函数的控制范围.