XDocument 到 DataTable 异步
XDocument to DataTable async
我正在将 100-500 XML 个文件中的数据加载到 DataTable
中以填充 Infragistics UltraGrid。这些文件小至 100K,大至 2MB。我一直在研究加快加载时间并考虑异步的方法,但数据table不是线程安全的。
从流程上看,加载xdoc大约花费了2/3的时间,另外1/3在读取xdoc和添加数据到table.
有没有一种方法可以使用异步并以某种方式加载下一个 XDocument
,同时读取前一个并将其加载到数据 table 中?我已经看过,但我没有看到一个很好的方法来做到这一点。我应该考虑其他一些策略吗?
这是我正在做的事情的简化版本:
private void openXMLs(){
OpenFileDialog openFileDialog1 = new OpenFileDialog
{
Title = "Browse XML files",
CheckFileExists = true,
CheckPathExists = true,
DefaultExt = "xml",
Filter = @"XML files (*.xml)|*.xml",
FilterIndex = 2,
RestoreDirectory = true,
ReadOnlyChecked = true,
ShowReadOnly = true,
Multiselect = true
};
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
foreach (String file in openFileDialog1.FileNames)
{
XDocument xdoc = XDocument.Load(file)
loadData(xdoc)
}
}
}
private void loadData(XDocument xdoc){
var query = xdoc.Descendants("root").AsEnumerable()
.Select(c => new
{
id = c.Element("ID").Value,
firstname = c.Element("firstname").Value,
lastname = c.Element("lastname").Value,
state = c.Element("state").Value,
});
foreach (var item in query)
{
_dt.Rows.Add(
item.id,
item.firstname,
item.lastname,
item.state
);
}
}
当然,这个问题没有唯一的答案。您可以通过多种方式完成,这里是一种:
private async Task openXMLs() {
// .. dialog ..
var loadTasks = new List<Task>();
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
foreach (String file in openFileDialog1.FileNames)
{
var xmlFile = XmlReader.Create(file);
XDocument xdoc = await XDocument.LoadAsync(xmlFile, LoadOptions.None, CancellationToken.None);
loadTasks.Add(Task.Run(() => loadData(xdoc));
}
}
await Task.WhenAll(loadTasks);
}
但是,数据表不是线程安全的,您需要锁定对它的访问。这也不保证添加文档的顺序。
private void loadData(XDocument xdoc){
var query = xdoc.Descendants("root").AsEnumerable()
.Select(c => new
{
id = c.Element("ID").Value,
firstname = c.Element("firstname").Value,
lastname = c.Element("lastname").Value,
state = c.Element("state").Value,
});
lock(_dt) {
foreach (var item in query)
{
_dt.Rows.Add(
item.id,
item.firstname,
item.lastname,
item.state
);
}
}
}
这将在下一个从磁盘读取时加载当前文档。
您可以从文件系统并行加载下一个 XDocument
,同时将上一个文档加载到 DataTable
,方法是使用如下所示的 ParallelForEachPairwise
扩展方法:
/// <summary>
/// Invokes two consecutive actions on each element in the source sequence.
/// The first action is invoked in parallel with the second action for the
/// previous element, and with fetching the sequence's next element.
/// </summary>
public static void ParallelForEachPairwise<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, TResult> offloadedAction,
Action<TResult> synchronousAction)
{
using var enumerator = source.GetEnumerator();
if (!enumerator.MoveNext()) return;
Task<TResult> task = Offload(enumerator.Current);
try
{
while (enumerator.MoveNext())
{
TResult result = task.GetAwaiter().GetResult();
task = Offload(enumerator.Current);
synchronousAction(result);
}
synchronousAction(task.GetAwaiter().GetResult());
}
finally { task.GetAwaiter().GetResult(); } // Prevent fire-and-forget
Task<TResult> Offload(TSource item) => Task.Run(() => offloadedAction(item));
}
ParallelForEachPairwise
接受两个委托作为参数。第一个委托 (offloadedAction
) 通过 Task.Run
方法卸载到 ThreadPool
。第二个委托 (synchronousAction
) 在当前线程上同步调用。这些委托彼此并行调用,但每个单独委托的重复调用是顺序的。所以你不必担心线程安全,前提是每个委托不会产生对另一个委托可见的副作用。
用法示例:
openFileDialog1.FileNames.ParallelForEachPairwise(file =>
{
return XDocument.Load(file);
}, xdoc =>
{
loadData(xdoc);
});
...或更简洁:
openFileDialog1.FileNames.ParallelForEachPairwise(XDocument.Load, loadData);
我正在将 100-500 XML 个文件中的数据加载到 DataTable
中以填充 Infragistics UltraGrid。这些文件小至 100K,大至 2MB。我一直在研究加快加载时间并考虑异步的方法,但数据table不是线程安全的。
从流程上看,加载xdoc大约花费了2/3的时间,另外1/3在读取xdoc和添加数据到table.
有没有一种方法可以使用异步并以某种方式加载下一个 XDocument
,同时读取前一个并将其加载到数据 table 中?我已经看过,但我没有看到一个很好的方法来做到这一点。我应该考虑其他一些策略吗?
这是我正在做的事情的简化版本:
private void openXMLs(){
OpenFileDialog openFileDialog1 = new OpenFileDialog
{
Title = "Browse XML files",
CheckFileExists = true,
CheckPathExists = true,
DefaultExt = "xml",
Filter = @"XML files (*.xml)|*.xml",
FilterIndex = 2,
RestoreDirectory = true,
ReadOnlyChecked = true,
ShowReadOnly = true,
Multiselect = true
};
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
foreach (String file in openFileDialog1.FileNames)
{
XDocument xdoc = XDocument.Load(file)
loadData(xdoc)
}
}
}
private void loadData(XDocument xdoc){
var query = xdoc.Descendants("root").AsEnumerable()
.Select(c => new
{
id = c.Element("ID").Value,
firstname = c.Element("firstname").Value,
lastname = c.Element("lastname").Value,
state = c.Element("state").Value,
});
foreach (var item in query)
{
_dt.Rows.Add(
item.id,
item.firstname,
item.lastname,
item.state
);
}
}
当然,这个问题没有唯一的答案。您可以通过多种方式完成,这里是一种:
private async Task openXMLs() {
// .. dialog ..
var loadTasks = new List<Task>();
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
foreach (String file in openFileDialog1.FileNames)
{
var xmlFile = XmlReader.Create(file);
XDocument xdoc = await XDocument.LoadAsync(xmlFile, LoadOptions.None, CancellationToken.None);
loadTasks.Add(Task.Run(() => loadData(xdoc));
}
}
await Task.WhenAll(loadTasks);
}
但是,数据表不是线程安全的,您需要锁定对它的访问。这也不保证添加文档的顺序。
private void loadData(XDocument xdoc){
var query = xdoc.Descendants("root").AsEnumerable()
.Select(c => new
{
id = c.Element("ID").Value,
firstname = c.Element("firstname").Value,
lastname = c.Element("lastname").Value,
state = c.Element("state").Value,
});
lock(_dt) {
foreach (var item in query)
{
_dt.Rows.Add(
item.id,
item.firstname,
item.lastname,
item.state
);
}
}
}
这将在下一个从磁盘读取时加载当前文档。
您可以从文件系统并行加载下一个 XDocument
,同时将上一个文档加载到 DataTable
,方法是使用如下所示的 ParallelForEachPairwise
扩展方法:
/// <summary>
/// Invokes two consecutive actions on each element in the source sequence.
/// The first action is invoked in parallel with the second action for the
/// previous element, and with fetching the sequence's next element.
/// </summary>
public static void ParallelForEachPairwise<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, TResult> offloadedAction,
Action<TResult> synchronousAction)
{
using var enumerator = source.GetEnumerator();
if (!enumerator.MoveNext()) return;
Task<TResult> task = Offload(enumerator.Current);
try
{
while (enumerator.MoveNext())
{
TResult result = task.GetAwaiter().GetResult();
task = Offload(enumerator.Current);
synchronousAction(result);
}
synchronousAction(task.GetAwaiter().GetResult());
}
finally { task.GetAwaiter().GetResult(); } // Prevent fire-and-forget
Task<TResult> Offload(TSource item) => Task.Run(() => offloadedAction(item));
}
ParallelForEachPairwise
接受两个委托作为参数。第一个委托 (offloadedAction
) 通过 Task.Run
方法卸载到 ThreadPool
。第二个委托 (synchronousAction
) 在当前线程上同步调用。这些委托彼此并行调用,但每个单独委托的重复调用是顺序的。所以你不必担心线程安全,前提是每个委托不会产生对另一个委托可见的副作用。
用法示例:
openFileDialog1.FileNames.ParallelForEachPairwise(file =>
{
return XDocument.Load(file);
}, xdoc =>
{
loadData(xdoc);
});
...或更简洁:
openFileDialog1.FileNames.ParallelForEachPairwise(XDocument.Load, loadData);