第一次使用 Parallel.ForEach UI 仍然锁定

First time using Parallel.ForEach UI still locking up

我想在这里做的是循环一个充满 URL 的列表视图,检查源代码是否包含字符串,如果包含则用 YES 或 [= 更新 UI 列表视图14=].

我忘记了 Parallel.ForEach 方法,所以决定尝试一下(我什至不确定这是否是最佳解决方案)

Parallel.ForEach(listViewMain.Items.Cast<ListViewItem>(), row =>
{
    try
    {

        string html = Helpers.GetRequest(row.Text);

        if (html.Contains(txtBoxFind.Text))
        {
            row.SubItems[3].Text = "YES";
        }
        else
        {
            row.SubItems[3].Text = "NO";
        }

    } catch(Exception) {

    }
});

如果没有 Parallel.ForEach,这个过程相当简单,但是 UI 仍然锁定,我实现了吗? Helpers.GetRequest 只是 returns 要检查的原始 HTML,我认为使用 Parallel.ForEach 会在处理时停止 UI 锁定,或者我弄错了,任何感谢帮助。

Parallel.ForEach 正在 UI 线程(当前线程)上执行,在非阻塞 UI 的情况下,它不会为您提供更多性能。如果你想避免 UI 块,你可以尝试使用 async 方法,例如:

Task.Run(() => CheckItems());

鉴于您可以实现 GetRequest 方法的异步版本,您可以实现一个异步方法来执行此操作,例如:

public async Task CheckItems()
{
    foreach (var row in listViewMain.Items.Cast<ListViewItem>())
    {
        try
        {    
            string html = await Helpers.GetRequestAsync(row.Text);

            if (html.Contains(txtBoxFind.Text))
            {
                row.SubItems[3].Text = "YES";
            }
            else
            {
                row.SubItems[3].Text = "NO";
            }

        } catch(Exception ex) {
        }
    }
}

不要为此使用 Parallel.ForEach()Parallel.ForEach() 用于 CPU 绑定作品。不是 IO.

我会做类似的事情:(我没有测试它,可能包含一些错字)(使用记事本)

所以你可以用这个作为想法:

public async void Button_Click(object sender, EventArg e)
{
    await CheckItems(listViewMain.Items.Cast<ListViewItem>());
}

public async Task CheckItems(IEnumerable<ListViewItem> items)
{
    // Capture the UI thread synchronization.
    var context = SynchronizationContext.Current;

    var tasks = new List<Task>();

    // create tasks.
    foreach (var row in items)
    {
        tasks.Add(Task.Run(() =>
        {
            // the lookup on a (probably) threadpool thread
            string html = Helpers.GetRequest(row.Text);

            // the processing here..
            var containsText = html.Contains(txtBoxFind.Text);

            // post the result (and touching gui items in the UI thread)
            // this.Invoke() is also and might be the best solution.
            context.Post(() =>
            {
                if (containsText)
                {
                    row.SubItems[3].Text = "YES";
                }
                else
                {
                    row.SubItems[3].Text = "NO";
                }
            });

        }));
    }

    // wait for them
    await Task.WhenAll(tasks);
}

虽然我在添加一些评论,但您也可以为此使用 this.Invoke()(而不是 SynchronizationContext

gtg.

在我们开始之前,请注意 Parallel.ForEach 本身是一个阻塞调用,这就是为什么您遇到 UI 没有响应的原因。

MS 文档中的 Avoid Executing Parallel Loops on the UI Thread 是一个很好的读物:

... the parallel loop blocks the UI thread on which it’s executing until all iterations are complete.

也就是说,如果不处理 CPU 绑定工作而是处理 I/O 绑定工作,您应该使用基于 Task 的方法。由于您正在处理网络呼叫,因此您应该坚持执行任务。试试这个:

    public async Task DoSomething()
    {
        // Process the items parallel
        await Task.WhenAll(listViewMain.Items.Cast<ListViewItem>().Select(async row =>
        {
            // wrap the long running call in a async Task
            string html = await Task.Run(() => Helpers.GetRequest(row.Text));

            // no need for context capturing and invokes, this is running on the UI thread
            var containsText = html.Contains(txtBoxFind.Text);
            if (containsText)
            {
                row.SubItems[3].Text = "YES";
            }
            else
            {
                row.SubItems[3].Text = "NO";
            }
        }));
    }

如果你能使 Helpers.GetRequest(row.Text) 成为基于任务的方法,那就更好了,你可以这样做:

    public async Task DoSomething()
    {
        // Process the items parallel
        await Task.WhenAll(listViewMain.Items.Cast<ListViewItem>().Select(async row =>
        {
            // wrap the long running call in a async Task
            string html = await Helpers.GetRequestAsync(row.Text);

            // no need for context capturing and invokes, this is running on the UI thread
            var containsText = html.Contains(txtBoxFind.Text);
            if (containsText)
            {
                row.SubItems[3].Text = "YES";
            }
            else
            {
                row.SubItems[3].Text = "NO";
            }
        }));
    }

但我们需要查看 Helpers.GetRequest(row.Text) 的代码来帮助您。

编辑

您已经显示了 GetRequest 的代码。 WebClient 不是基于任务的,请尝试 HttpClient:

public async Task<string> GetRequestAsync(string url)
{
    var html = "";
    using (HttpClient wc = new HttpClient())
    {
        html = await wc.GetStringAsync(url);
    }
    return html;
}