Parallel.ForEach和DataGridViewRow

3
我遇到了将AsParallel转换为Parallel.ForEach的问题。 我有一个DataGridView,在第一列中放置了一些值,然后使用ForEach循环将该值发送到方法并获取返回值,然后将返回值放置在第二列中。
一开始,我使用ForEach循环,但它花费太多时间,然后我决定使用AsParallel,但在我的情况下,使用Parallel.ForEach可能更好,但我无法使其与datagridviewrow一起工作。
ForEach方法:
 foreach (DataGridViewRow dgvRow in dataGrid1.Rows)
 {
     // SOME CODES REMOVED FOR CLARITY
     string data1 = row.Cells[1].Value;
     var returnData = getHtml(data1);
     row.Cells[2].Value = returnData;
 }

AsParallel Method :

dataGrid1.Rows.Cast<DataGridViewRow>().AsParallel().ForAll(row =>
{
    // SOME CODES REMOVED FOR CLARITY
    string data1 = row.Cells[1].Value;
    var returnData = getHtml(data1);
    row.Cells[2].Value = returnData;
}); 

那么,我该如何使用Parallel.ForEach循环与DataGridViewRow(DataGridView)一起使用呢?
谢谢。

2
通常在使用用户界面组件时,需要在主线程上与它们交互。有时候你会发现在多个线程上与其交互并不会出现什么问题,但这可能会看起来“有 bug”。你真的应该使用支持多线程的数据源而不是直接与网格行交互。 - The Muffin Man
这里的瓶颈是什么:获取/设置单元格值还是执行getHtml? - Andrey Nasonov
1个回答

8
如果getHtml(以及循环中的其他非UI部分)相对昂贵,那么并行处理就是明智的选择;如果它很便宜,那么并行处理就没有意义,因为更新UI(您的数据网格)需要顺序执行,因为只有UI线程才能更新它。如果getHtml(以及循环中的其他非UI部分)相对昂贵,则可以采取以下措施:
var current_synchronization_context = TaskScheduler.FromCurrentSynchronizationContext();

Task.Factory.StartNew(() => //This is important to make sure that the UI thread can return immediately and then be able to process UI update requests
{
    Parallel.ForEach(dataGrid1.Rows.Cast<DataGridViewRow>(), row =>
    {
        // SOME CODES REMOVED FOR CLARITY
        string data1 = row.Cells[1].Value;
        var returnData = getHtml(data1); //expensive call

        Task.Factory.StartNew(() => row.Cells[2].Value = returnData,
            CancellationToken.None,
            TaskCreationOptions.None,
            current_synchronization_context); //This will request a UI update on the UI thread and return immediately
    }); 
});

创建一个 Task 并使用 TaskScheduler.FromCurrentSynchronizationContext() 在 Windows Forms 应用程序和 WPF 应用程序中都能正常工作。

如果您不想为每个 UI 更新安排一个 Task,您可以直接调用 BeginInvoke 方法(如果这是一个 Windows Forms 应用程序),像这样:

dataGrid1.BeginInvoke((Action)(() =>
{
    row.Cells[2].Value = returnData;
}));

我的建议会导致在处理/生成数据的同时将数据呈现到UI上。
如果您不关心这一点,并且您可以先处理所有数据,然后再更新UI,则可以执行以下操作:
1)在UI线程中收集来自UI的所有数据
2)通过Parallel.ForEach处理该数据,并将结果存储在数组中
3)从UI线程呈现数据到UI上。

我们如何确保数组中数据的顺序? - Yacoub Massad
string[] res = new string[dataGrid1.Rows.Count]; Parallel.For(0, res.Length, index => { ... } - Andrey Nasonov
否定。我在回答开头假设非 UI 工作(例如 getHtml)的成本相对较高。这意味着 UI 线程是放松的,因为它没有足够的每秒请求来忙碌。 - Yacoub Massad
谢谢@YacoubMassad和AndreyNasonov,我从你们身上学到了很多东西。我想我应该再解释一下为什么我要使用parallel.foreach..getHtml方法。getHtml方法是一个简单的httpwebrequest,返回页面的代码,然后用HTML Agility Pack来收集一些数据。现在它给我一个返回值,但我会添加至少6个请求getHtml,从测试中我发现如果我为getHtml添加5-6个(每个不同)请求,我必须等待0.5秒以上才能获得每行数据,而使用EachFor循环将需要很长时间…这就是为什么我想要使用多线程的原因。 - LikePod
好的。谢谢你的时间。顺便说一下,对于你的问题的答案:1.数据必须在完成url(requests)线程后仅更新一次。2.是的,我正在从csv文件中导入url列表,然后用开始按钮启动循环。这确实是一个简单的程序。一个datagridview(之前是listview),3个按钮(开始、导入到dgv和清除按钮),这就是整个程序。我害怕这个问题会得到很多踩。 - LikePod
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接