后台工作块 UI
Backgroundworker blocks UI
我尝试在另一个后台线程中执行一个简单的任务,因此 UI 没有被阻塞,但它仍然被阻塞。我是不是忘记了什么?
public partial class backgroundWorkerForm : Form
{
public backgroundWorkerForm()
{
InitializeComponent();
}
private void doWorkButton_Click(object sender, EventArgs e)
{
if (backgroundWorker.IsBusy != true)
{
// Start the asynchronous operation.
backgroundWorker.RunWorkerAsync();
}
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//BackgroundWorker worker = sender as BackgroundWorker;
if (textBoxOutput.InvokeRequired)
{
textBoxOutput.Invoke(new MethodInvoker(delegate
{
for (int i = 0; i < 10000; i++)
{
textBoxOutput.AppendText(i + Environment.NewLine);
}
}));
}
}
}
当文本框被填充时,UI 被阻止:
一种更简单的方法是完全创建您的输出文本,然后将完整的输出粘贴到文本框中,然后您只需要调用一次
protected delegate void SetTextDelegate(TextBox tb, string Text);
protected void SetText(TextBox tb, string Text)
{
if (tb.InvokeRequired) {
tb.Invoke(new SetTextDelegate(SetText), tb, Text);
return;
}
tb.Text = Text;
}
然后在你的工作中
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
StringBuilder sb = new StringBuilder();
//BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 0; i < 10000; i++)
{
sb.AppendLine(i.ToString());
}
SetText(textBoxOutput, sb.ToString());
}
您的调用代码应该在循环之外。调用的代码块中的所有内容都将在 UI 线程上执行,从而阻塞它。
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 0; i < 10000; i++)
{
// do long-running task
//if (textBoxOutput.InvokeRequired)
//{
textBoxOutput.Invoke(new MethodInvoker(delegate
{
textBoxOutput.AppendText(i + Environment.NewLine);
}));
//}
}
}
您的应用想要从后台线程向 UI 重复发送更新。为此有一个内置机制:后台工作者的 ProgressChanged
事件。 ReportProgress
调用在后台触发,但在 UI 线程上执行。
不过,我确实改变了一件事。过多的跨线程调用会降低性能。因此,我不是每次迭代都发送更新,而是将它们分批为 100。
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
const int maxIterations = 10000;
var progressLimit = 100;
var staging = new List<int>();
for (int i = 0; i < maxIterations; i++)
{
staging.Add(i);
if (staging.Count % progressLimit == 0)
{
// Only send a COPY of the staging list because we
// may continue to modify staging inside this loop.
// There are many ways to do this. Below is just one way.
backgroundWorker1.ReportProgress(staging.Count, staging.ToArray());
staging.Clear();
}
}
// Flush last bit in staging.
if (staging.Count > 0)
{
// We are done with staging here so we can pass it as is.
backgroundWorker1.ReportProgress(staging.Count, staging);
}
}
// The ProgressChanged event is triggered in the background thread
// but actually executes in the UI thread.
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage == 0) return;
// We don't care if an array or a list was passed.
var updatedIndices = e.UserState as IEnumerable<int>;
var sb = new StringBuilder();
foreach (var index in updatedIndices)
{
sb.Append(index.ToString() + Environment.NewLine);
}
textBoxOutput.Text += sb.ToString();
}
编辑:
这需要您将后台工作人员的 WorkerReportsProgress
属性 设置为 true。
通过 ReportProgress 调用传递计数并不重要。我这样做只是为了拥有一些东西并快速检查我是否可以 return.
确实应该记住有多少事件正在被调用和排队。 textBoxOutput
,您的原始应用有 10,000 次跨线程调用和 10,000 次更改的文本事件。我的示例使用 100 个跨线程调用,因为我使用的页面大小为 100。我仍然可以为文本框生成 10,000 个更改的文本事件,而是使用 StringBuilder 对象来保存整页更改,然后为此更新一次文本框页。这样文本框只有 100 个更新事件。
编辑 2
您的应用是否需要分页并不是主要问题。最大的收获应该是后台工作人员在尝试将信息传回 UI 时确实应该使用 ReportProgress
。看到这个 MSDN Link。特别要注意的是:
You must be careful not to manipulate any user-interface objects in
your DoWork event handler. Instead, communicate to the user interface
through the ProgressChanged and RunWorkerCompleted events.
我尝试在另一个后台线程中执行一个简单的任务,因此 UI 没有被阻塞,但它仍然被阻塞。我是不是忘记了什么?
public partial class backgroundWorkerForm : Form
{
public backgroundWorkerForm()
{
InitializeComponent();
}
private void doWorkButton_Click(object sender, EventArgs e)
{
if (backgroundWorker.IsBusy != true)
{
// Start the asynchronous operation.
backgroundWorker.RunWorkerAsync();
}
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//BackgroundWorker worker = sender as BackgroundWorker;
if (textBoxOutput.InvokeRequired)
{
textBoxOutput.Invoke(new MethodInvoker(delegate
{
for (int i = 0; i < 10000; i++)
{
textBoxOutput.AppendText(i + Environment.NewLine);
}
}));
}
}
}
当文本框被填充时,UI 被阻止:
一种更简单的方法是完全创建您的输出文本,然后将完整的输出粘贴到文本框中,然后您只需要调用一次
protected delegate void SetTextDelegate(TextBox tb, string Text);
protected void SetText(TextBox tb, string Text)
{
if (tb.InvokeRequired) {
tb.Invoke(new SetTextDelegate(SetText), tb, Text);
return;
}
tb.Text = Text;
}
然后在你的工作中
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
StringBuilder sb = new StringBuilder();
//BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 0; i < 10000; i++)
{
sb.AppendLine(i.ToString());
}
SetText(textBoxOutput, sb.ToString());
}
您的调用代码应该在循环之外。调用的代码块中的所有内容都将在 UI 线程上执行,从而阻塞它。
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 0; i < 10000; i++)
{
// do long-running task
//if (textBoxOutput.InvokeRequired)
//{
textBoxOutput.Invoke(new MethodInvoker(delegate
{
textBoxOutput.AppendText(i + Environment.NewLine);
}));
//}
}
}
您的应用想要从后台线程向 UI 重复发送更新。为此有一个内置机制:后台工作者的 ProgressChanged
事件。 ReportProgress
调用在后台触发,但在 UI 线程上执行。
不过,我确实改变了一件事。过多的跨线程调用会降低性能。因此,我不是每次迭代都发送更新,而是将它们分批为 100。
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
const int maxIterations = 10000;
var progressLimit = 100;
var staging = new List<int>();
for (int i = 0; i < maxIterations; i++)
{
staging.Add(i);
if (staging.Count % progressLimit == 0)
{
// Only send a COPY of the staging list because we
// may continue to modify staging inside this loop.
// There are many ways to do this. Below is just one way.
backgroundWorker1.ReportProgress(staging.Count, staging.ToArray());
staging.Clear();
}
}
// Flush last bit in staging.
if (staging.Count > 0)
{
// We are done with staging here so we can pass it as is.
backgroundWorker1.ReportProgress(staging.Count, staging);
}
}
// The ProgressChanged event is triggered in the background thread
// but actually executes in the UI thread.
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage == 0) return;
// We don't care if an array or a list was passed.
var updatedIndices = e.UserState as IEnumerable<int>;
var sb = new StringBuilder();
foreach (var index in updatedIndices)
{
sb.Append(index.ToString() + Environment.NewLine);
}
textBoxOutput.Text += sb.ToString();
}
编辑:
这需要您将后台工作人员的 WorkerReportsProgress
属性 设置为 true。
通过 ReportProgress 调用传递计数并不重要。我这样做只是为了拥有一些东西并快速检查我是否可以 return.
确实应该记住有多少事件正在被调用和排队。 textBoxOutput
,您的原始应用有 10,000 次跨线程调用和 10,000 次更改的文本事件。我的示例使用 100 个跨线程调用,因为我使用的页面大小为 100。我仍然可以为文本框生成 10,000 个更改的文本事件,而是使用 StringBuilder 对象来保存整页更改,然后为此更新一次文本框页。这样文本框只有 100 个更新事件。
编辑 2
您的应用是否需要分页并不是主要问题。最大的收获应该是后台工作人员在尝试将信息传回 UI 时确实应该使用 ReportProgress
。看到这个 MSDN Link。特别要注意的是:
You must be careful not to manipulate any user-interface objects in your DoWork event handler. Instead, communicate to the user interface through the ProgressChanged and RunWorkerCompleted events.