Backgroundworker块UIBackgroundworker blocks UI

- 此内容更新于:2015-01-06
主题:

我试着其他backgroundthread执行一项简单的任务,因此,界面没有被封锁,但它仍然被封锁。我什么都忘记了吗? 当文本框填满,阻塞UI:

原文:

I try to perform an easy task in an other backgroundthread, so the UI doesn't get blocked, but it still gets blocked. Did I forget anything?

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

While the textBox gets filled, the UI is blocked:

enter image description here

Icepickle的回复:难怪,你有工作的循环在你调用(就在UI线程上调用)

(原文:No wonder, you have the loop that does the work inside your Invoke (which gets invoked on the UI thread))

Ben Robinson的回复:你的背景工人做的唯一的事就是整个循环到UI线程调度执行它。

(原文:The only thing your background worker does is dispatch the entire loop to the UI thread an execute it there.)

Sriram Sakthivel的回复:textBoxOutput。调用将在UI线程同步执行代表,因此,街区。# 39;实质性的年代不清楚你# 39;你想实现,但显然你不要# 39;不需要BackgroundWorker。

(原文:textBoxOutput.Invoke will execute the delegate synchronously in UI thread and thus it blocks. It's not clear what you're trying to achieve but apparently you don't need a BackgroundWorker at all.)

Amit的回复:我认为你需要单独的背景工作处理和UI升级。为什么可以# 39;使用OnRunWorkerCompleted UI升级吗?

(原文:I think you need to separate background working processing and UI updation. Why can't use OnRunWorkerCompleted for UI updation?)

Sriram Sakthivel的回复:一种想法是生成字符串在内存中并更新一次文本框。这将表现更好,但很难回答不知道你# 39;努力实现。

(原文:One idea is to generate the string in memory and update it once to TextBox. That will perform better but hard to answer without knowing what you're trying to achieve.)

解决方案:
调用代码应该外循环。codeblock调用的一切,将在UI线程上执行,从而阻止它。
原文:

Your invocation code should be outside the loop. Everything in the invoked codeblock, will be executed on the UI thread, thus blocking it.

    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);
                }));
            //}
        }
    }
Sriram Sakthivel的回复:不知道为什么这是upvoted。这段代码不会改善什么,而是使它更糟。1

(原文:Not sure why this was upvoted. This code is not going to improve anything, rather it makes it even worse. -1)

codemonkey的回复:@sriram-sakthivel:你应该试试代码。它也# 39;t块UI。

(原文:@sriram-sakthivel: you should try the code. It doesn't block the UI any more.)

Daniel Kelley的回复:出于兴趣,你为什么叫InvokeRequired内部循环吗?你相信每个循环迭代之间将会改变吗?

(原文:Out of interest, why do you call InvokeRequired inside the loop? What do you believe will change between each loop iteration?)

Sriram Sakthivel的回复:也许这个新版本的操作系统,但这仍然使用UI线程更新代码。不会在所有的操作系统工作吗

(原文:Maybe newer version of operating systems does this, but this code still uses UI thread to update. Will not work in all operating systems)

解决方案:
原文:

an easier way would be to do completely create your output text, and then paste the full output into the TextBox, then you only need one invocation

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

and then inside your dowork

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());
}
Sriram Sakthivel的回复:这变化,这如何回答这个问题?

(原文:What does this changes and how does this answers the question?)

eMi的回复:@SriramSakthivel,如果你把一个thread . sleep(100);在我的代码AppendText后,用户界面将会封锁,我必须等到所有10000个数字,但这些代码并不阻止

(原文:@SriramSakthivel , if you put a Thread.Sleep(100); in my code after the AppendText, the UI will be blocked, and I have to wait until all 10000 numbers are added, but with this code it doesnt block)

Sriram Sakthivel的回复:尝试运行您的代码,看是否UI模块。这段代码做什么OP在一个相当复杂的方式。

(原文:Try running your code and see whether UI blocks or not. This code does exactly what OP did in a rather complicated way.)

Icepickle的回复:@SriramSakthivel什么改变是,它会调用10000次(2线),所以它应该运行顺畅,在OP开始后,整个循环中执行一个调用(因此UI线程并# 39;t有机会更新)

(原文:@SriramSakthivel what changes is the fact that it gets invoked 10000 times (2 threads) and so it should run smoother, where as in the OP start post, the full loop was executed in one invoke (thus the UI thread didn't have a chance to update))

Sriram Sakthivel的回复:这段代码获得# 39;t帮助OP。这仍然在UI线程调用10000次迭代。老实说这将花费更多的时间比op # 39;代码由于10000调用调用。

(原文:This code won't help OP in anyway. This still invokes 10000 iterations in UI thread. To be honest this will take more time than OP's code due to 10000 Invoke calls.)

解决方案:
原文:

Your app wants to repeatedly send updates from the background thread to the UI. There is a built-in mechanism for this: the ProgressChanged event for the background worker. A ReportProgress call is triggered in the background, but executes on the UI thread.

I do change one thing, however. Performance can degrade with too many cross-thread calls. So instead of sending an update every iteration, I instead will batch them into 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();
    }

EDIT:

This requires you set the background worker's WorkerReportsProgress property to true.

It's not important that you pass a count with the ReportProgress call. I do so just to have something and to quickly check if I can return.

One really should keep in mind about how many events are being invoked and queued up. Your original app had 10,000 cross thread invocations and 10,000 changed text events for textBoxOutput. My example uses 100 cross thread calls since I use a page size of 100. I could still have generated 10,000 changed text events for the textbox, but instead use a StringBuilder object to hold a full page of changes and then update the textbox once for that page. That way the textbox only has 100 update events.

EDIT 2

Whether or not your app needs paging is not the main deal. The biggest take away should be that the background worker really should use ReportProgress when trying to communicate info back to the UI. See this MSDN Link. Of particular note is this:

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.

eMi的回复:这似乎很好,对于我贴的这个特殊的测试程序,但我在真实的应用程序不能使用这种方法,我不要,不知道有多少更新事件将会发生。

(原文:this seems fine, for this particular test program that I posted, but in my real application I cannot use this approach, as I don't know how many update events will happen.)

Rick Davin的回复:使用我的方法(1)背景的工人不阻塞UI,和(2)最小化跨线程调用以及文本框TextChanged调用特别是在这种情况下,你不要,不知道有多少更新事件将会发生。这不是有用吗?

(原文:Using my approach (1) the background worker does not block the UI, and (2) minimizes cross thread calls as well as textbox TextChanged calls particularly in cases where you don't know how many update events will happen. How is this not helpful?)

eMi的回复:我不# 39;t(和可以# 39;t)变量:maxIterations,progressLimit,我也不需要任何进展报告。也许对这个特别的代码我了,但我不能适应你的方法在我的应用程序。反正我# 39;我给你+ 1对你的努力,谢谢

(原文:I don't have (and can't have) variables: maxIterations , progressLimit, nor I need to report any progress. It is maybe helpful for this particularly piece of code I posted, but I cannot adapt your approach into my application. Anyway I'l give you a +1 for your effort, thanks)

Rick Davin的回复:我强烈建议您阅读我的编辑2。

(原文:I strongly suggest you read my Edit 2.)

解决方案:
添加Appliction.DoEvents()之后,调用的调用方法: textBoxOutput。调用(新MethodInvoker(代表 { textBoxOutput。AppendText(我+ Environment.NewLine); })); Application.DoEvents(); 这将允许应用程序来处理队列中的所有消息。
原文:

Add Appliction.DoEvents() after call of Invoke method:

textBoxOutput.Invoke(new MethodInvoker(delegate { textBoxOutput.AppendText(i + Environment.NewLine); })); Application.DoEvents();

This will allow the application to process all messages in the queue.

eMi的回复:微软对其使用警告

(原文:Microsoft cautions against its use)

Sriram Sakthivel的回复:参考这个答案理解为什么它使用Application.DoEvents不好

(原文:Refer this answer to understand why it is bad to use Application.DoEvents)