为什么我在使用迭代器的并行任务执行期间得到重复值?
Why I get duplicated values during a Parallel Task Execution using an Iterator?
我想使用 Task Parallel Library (TPL).
从一个简单的 XML 文件(见下文)中检索唯一客户 ID 列表
我使用 XPathNavigator to iterate through xml and retrieve customer Ids. I’m using an iterator with the Parallel.ForEach(..) 进行任务并行处理。
出于某种原因,我检索到了重复的客户 ID。它几乎看起来像迭代器跟踪前一个 reads/iteratoes。我每次循环时都期待新的迭代器。
试了很多方法还是不行。如果有人能指出我正确的方向,将不胜感激。
(尝试的完整代码示例如下。)
一些简单的XML:
private static string Xml()
{
return "<persons>" +
"<person><id>1</id></person>" +
"<person><id>2</id></person>" +
"<person><id>3</id></person>" +
"<person><id>4</id></person>" +
"<person><id>5</id></person>" +
"</persons>";
}
static void Main(string[] args)
{
var navigator = XmlHelper.CreateNavigator(Xml());
string xpath = "/persons/person";
var exp = navigator.Compile(xpath);
var iterator = navigator.Select(exp);
//Parallel Task scenario returns duplicated customer Ids
Parallel.ForEach(Iterate(iterator), (a) =>
{
string xpathId = "/person/id";
var value = XmlHelper.SelectString(a.Current, xpathId);
Console.WriteLine("person id: " + value);
});
/*
* Sample output can be: (notice the duplicated values!)
* person id: 2
* person id: 2
* person id: 4
* person id: 4
* person id: 3
* person id: 1
*
*/
//Sequential scenario displays unique values:
//while (iterator.MoveNext())
//{
// string xpathId = "/person/id";
// var value = XmlHelper.SelectString(iterator.Current, xpathId);
// Console.WriteLine("person id: " + value);
//}
Console.ReadLine();
}
private static IEnumerable<XPathNodeIterator>
Iterate(XPathNodeIterator iterator)
{
while (iterator.MoveNext())
{
yield return iterator;
}
}
public static class XmlHelper
{
public static string SelectString(XPathNavigator navigator, string xpath)
{
return SelectString(navigator, xpath, null);
}
public static string SelectString
(XPathNavigator navigator, string xpath, string defaultVal)
{
XPathExpression exp = navigator.Compile(xpath);
XPathNodeIterator it = navigator.Select(exp);
it.MoveNext();
return it.Current.Value;
}
public static XPathNavigator CreateNavigator(string input)
{
XPathDocument doc;
using (var reader = new StringReader(input))
{
doc = new XPathDocument(reader);
}
return doc.CreateNavigator();
}
}
请注意,我也采用了 this 文章所采用的方法,但仍然没有成功。
非常感谢任何帮助。
来自 MSDN:
Any public static (Shared in Visual Basic) members of this type are
thread safe. Any instance members are not guaranteed to be thread
safe.
https://msdn.microsoft.com/en-us/library/system.xml.xpath.xpathnavigator(v=vs.110).aspx
所以你的迭代器不是线程安全的,不能像这样使用。
问题的根源在于这个函数:
private static IEnumerable<XPathNodeIterator> Iterate(XPathNodeIterator iterator)
{
while (iterator.MoveNext())
{
yield return iterator;
}
}
如果你仔细想想这个函数,你就会得出结论,它有一些非常不对劲的地方。
这个函数实际上做的是:它给你一个迭代器,它给你 n
次对一个迭代器的引用。其中 n
是迭代器中应用的元素数量 属性.
这把一切都搞砸了。 Parallel.ForEach
很容易处理 Enumerables,但是你的函数所做的是多次应用一个迭代器。
我想你想做的是 "convert" 你的 Iterator 变成 IEnumerable
。但是你需要一个 IEnumerable
来给你迭代器的值,而不是一遍又一遍地给你迭代器的值。
总而言之,您的函数应该如下所示:
private static IEnumerable<XPathNavigator> Iterate(XPathNodeIterator iterator)
{
while (iterator.MoveNext())
{
yield return iterator.Current;
}
}
这样你的可枚举实际上包含你的迭代器的值和 returns 这个。使用此功能,您将获得循环中的所有条目。
感谢@Natram 和@Paddy!
两个答案都为我指明了正确的方向。我认为@Nitram 的回答更准确,因为他首先解释了我遇到的问题。
似乎 运行 并行,下面的代码仍然导致一些重复。这对于较小的集合并不明显,但是当数字变大时,它倾向于在多线程环境中重复值。
private static IEnumerable<XPathNavigator> Iterate(XPathNodeIterator iterator)
{
while (iterator.MoveNext())
{
yield return iterator.Current;
}
}
我相信这就是@Paddy 提到 Iterator 不是线程安全的原因。
@Ntram 提到:
Parallel.ForEach is easily able to handle Enumerables..
基于此,我继续将 Iterator 转换为 return XPathNaviagator Enumerables 列表
private static IEnumerable<XPathNavigator> Iterate(XPathNodeIterator iterator)
{
var items = iterator.Cast<XPathNavigator>();
return items;
}
这解决了我遇到的问题并且它有效地处理了我期望并行化的项目数。
我想使用 Task Parallel Library (TPL).
从一个简单的 XML 文件(见下文)中检索唯一客户 ID 列表我使用 XPathNavigator to iterate through xml and retrieve customer Ids. I’m using an iterator with the Parallel.ForEach(..) 进行任务并行处理。
出于某种原因,我检索到了重复的客户 ID。它几乎看起来像迭代器跟踪前一个 reads/iteratoes。我每次循环时都期待新的迭代器。
试了很多方法还是不行。如果有人能指出我正确的方向,将不胜感激。
(尝试的完整代码示例如下。)
一些简单的XML:
private static string Xml()
{
return "<persons>" +
"<person><id>1</id></person>" +
"<person><id>2</id></person>" +
"<person><id>3</id></person>" +
"<person><id>4</id></person>" +
"<person><id>5</id></person>" +
"</persons>";
}
static void Main(string[] args)
{
var navigator = XmlHelper.CreateNavigator(Xml());
string xpath = "/persons/person";
var exp = navigator.Compile(xpath);
var iterator = navigator.Select(exp);
//Parallel Task scenario returns duplicated customer Ids
Parallel.ForEach(Iterate(iterator), (a) =>
{
string xpathId = "/person/id";
var value = XmlHelper.SelectString(a.Current, xpathId);
Console.WriteLine("person id: " + value);
});
/*
* Sample output can be: (notice the duplicated values!)
* person id: 2
* person id: 2
* person id: 4
* person id: 4
* person id: 3
* person id: 1
*
*/
//Sequential scenario displays unique values:
//while (iterator.MoveNext())
//{
// string xpathId = "/person/id";
// var value = XmlHelper.SelectString(iterator.Current, xpathId);
// Console.WriteLine("person id: " + value);
//}
Console.ReadLine();
}
private static IEnumerable<XPathNodeIterator>
Iterate(XPathNodeIterator iterator)
{
while (iterator.MoveNext())
{
yield return iterator;
}
}
public static class XmlHelper
{
public static string SelectString(XPathNavigator navigator, string xpath)
{
return SelectString(navigator, xpath, null);
}
public static string SelectString
(XPathNavigator navigator, string xpath, string defaultVal)
{
XPathExpression exp = navigator.Compile(xpath);
XPathNodeIterator it = navigator.Select(exp);
it.MoveNext();
return it.Current.Value;
}
public static XPathNavigator CreateNavigator(string input)
{
XPathDocument doc;
using (var reader = new StringReader(input))
{
doc = new XPathDocument(reader);
}
return doc.CreateNavigator();
}
}
请注意,我也采用了 this 文章所采用的方法,但仍然没有成功。 非常感谢任何帮助。
来自 MSDN:
Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.
https://msdn.microsoft.com/en-us/library/system.xml.xpath.xpathnavigator(v=vs.110).aspx
所以你的迭代器不是线程安全的,不能像这样使用。
问题的根源在于这个函数:
private static IEnumerable<XPathNodeIterator> Iterate(XPathNodeIterator iterator)
{
while (iterator.MoveNext())
{
yield return iterator;
}
}
如果你仔细想想这个函数,你就会得出结论,它有一些非常不对劲的地方。
这个函数实际上做的是:它给你一个迭代器,它给你 n
次对一个迭代器的引用。其中 n
是迭代器中应用的元素数量 属性.
这把一切都搞砸了。 Parallel.ForEach
很容易处理 Enumerables,但是你的函数所做的是多次应用一个迭代器。
我想你想做的是 "convert" 你的 Iterator 变成 IEnumerable
。但是你需要一个 IEnumerable
来给你迭代器的值,而不是一遍又一遍地给你迭代器的值。
总而言之,您的函数应该如下所示:
private static IEnumerable<XPathNavigator> Iterate(XPathNodeIterator iterator)
{
while (iterator.MoveNext())
{
yield return iterator.Current;
}
}
这样你的可枚举实际上包含你的迭代器的值和 returns 这个。使用此功能,您将获得循环中的所有条目。
感谢@Natram 和@Paddy!
两个答案都为我指明了正确的方向。我认为@Nitram 的回答更准确,因为他首先解释了我遇到的问题。
似乎 运行 并行,下面的代码仍然导致一些重复。这对于较小的集合并不明显,但是当数字变大时,它倾向于在多线程环境中重复值。
private static IEnumerable<XPathNavigator> Iterate(XPathNodeIterator iterator)
{
while (iterator.MoveNext())
{
yield return iterator.Current;
}
}
我相信这就是@Paddy 提到 Iterator 不是线程安全的原因。
@Ntram 提到:
Parallel.ForEach is easily able to handle Enumerables..
基于此,我继续将 Iterator 转换为 return XPathNaviagator Enumerables 列表
private static IEnumerable<XPathNavigator> Iterate(XPathNodeIterator iterator)
{
var items = iterator.Cast<XPathNavigator>();
return items;
}
这解决了我遇到的问题并且它有效地处理了我期望并行化的项目数。