Backgroundworker阻止UI

时间:2015-01-05 13:02:03

标签: c# winforms backgroundworker

我尝试在其他后台线程中执行一个简单的任务,因此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);
                }
            }));
        }
    }
}

当textBox被填满时,UI被阻止:

enter image description here

3 个答案:

答案 0 :(得分:3)

您的应用想要重复从后台线程向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调用传递计数并不重要。我这样做只是为了得到一些东西,并快速检查我是否可以返回。

应该记住有多少事件被调用和排队。您的原始应用程序为textBoxOutput提供了10,000个跨线程调用和10,000个已更改的文本事件。我的示例使用了100个跨线程调用,因为我使用的页面大小为100.我仍然可以为文本框生成10,000个已更改的文本事件,而是使用StringBuilder对象来保存整页更改,然后为此更新文本框一次页。这样文本框只有100个更新事件。

编辑2

您的应用程序是否需要分页不是主要的交易。最大的收获应该是后台工作者在尝试将信息传回UI时真的应该使用ReportProgress。见MSDN Link。特别值得注意的是:

  

您必须小心不要操纵任何用户界面对象   你的DoWork事件处理程序。而是与用户界面进行通信   通过ProgressChanged和RunWorkerCompleted事件。

答案 1 :(得分:2)

您的调用代码应该在循环之外。调用的代码块中的所有内容都将在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);
                }));
            //}
        }
    }

答案 2 :(得分:1)

更简单的方法是完全创建输出文本,然后将完整输出粘贴到TextBox中,然后只需要一次调用

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