【发布时间】:2014-04-19 02:03:02
【问题描述】:
在 Linq Select 中使用 HttpClient.GetAsync 或其任何异步方法或任何 BCL 异步方法可能会导致一些奇怪的两次拍摄。
这里是一个单元测试用例:
[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);
}
测试将失败,因为 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);
}
如果我使用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);
}
显然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);
}
Scott 提到 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);
}
我猜 Linq Select 对 Task 有一些特殊处理。
毕竟,在 Linq 中触发多个异步方法并检查WaitAll 之后的结果的好方法是什么?
【问题讨论】:
-
您没有调用
items.ToArray()在您的测试方法中实现集合,因此迭代只发生一次(通过foreach)。斯科特的答案是正确的,你的测试不是。 -
您的第一个测试(使用任务)也不能代表您的实际代码,因为在测试中您正在迭代
tasks.ToArray()生成的数组(因此实际的tasks可枚举仅迭代一次 - 通过调用.ToArray()),而在原始代码示例中,您首先通过调用.ToArray()迭代tasks,然后通过foreach语句进行迭代。
标签: c# linq asynchronous parallel-processing task