第一次使用 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;
}
我想在这里做的是循环一个充满 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;
}