XDocument 到 DataTable 异步

Posted

技术标签:

【中文标题】XDocument 到 DataTable 异步【英文标题】:XDocument to DataTable async 【发布时间】:2021-11-04 17:30:26 【问题描述】:

我正在将 100-500 个 XML 文件中的数据加载到 DataTable 以填充 Infragistics UltraGrid。这些文件小至 100K,大至 2MB。我一直在寻找加快加载时间并考虑异步的方法,但数据表不是线程安全的。

看流程,加载xdoc大约需要2/3的时间,另外1/3是在读取xdoc和向表中添加数据。

有没有办法使用异步并以某种方式加载下一个 XDocument 而前一个被读取并加载到数据表中?我看过,但我没有看到一个很好的方法来为每个人做到这一点。还有其他一些我应该考虑的策略吗?

这是我正在做的简化版本:

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
        );
    

【问题讨论】:

有些相关:How to enumerate an IAsyncEnumerable<T> and invoke an async action for each element, allowing concurrency for each iteration/action pair? 我正在将 100-500 个 XML 文件中的数据加载到 DataTable 中 很遗憾,这是我必须工作的环境。 【参考方案1】:

当然,这个问题没有单一的答案。您可以通过多种方式做到这一点,这里是一种:

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.LoadAsync。我错过了什么吗?将其保留为 XDocument.Load 会有问题吗? 不幸的是,LoadAsync 似乎没有过载来接受文件参数,但我认为你可以使用XmlReader。更新了我的答案。【参考方案2】:

您可以使用如下所示的ParallelForEachPairwise 扩展方法并行加载文件系统中的下一个XDocument,同时将上一个文档加载到DataTable

/// <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);

【讨论】:

这看起来是一个有趣的方法,我也会尝试测试它,看看它如何与其他方法叠加。谢谢! @Fisher8370 这是一种保守的并行方法。您可以期望它最多可将性能提升 2 倍,但它几乎是免费的。与简单的顺序方法相比,存储驱动程序和 CPU 不会因大量额外工作而繁重。

以上是关于XDocument 到 DataTable 异步的主要内容,如果未能解决你的问题,请参考以下文章

html 从服务器到Datatable插件1.10的半异步数据

html 从服务器到Datatable插件1.10的半异步数据

如何实现bootstrap jquery dataTable异步ajax刷新表格数据

使用 Xdocument 进行简单的 Xml 解析

将 XElement 合并到 XDocument 并解析命名空间

C# 将根添加到 XDocument