C#线程使用invoke,冻结窗体

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14569482/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 12:18:06  来源:igfitidea点击:

C# Threading using invoke, freezing the form

c#multithreadinginvoke

提问by Random IT Guy

I'm trying to use threads and prevent the program from freezing while the thread is busy. It should show the progress (writing of 0's / 1's) and not just show the result after its done, freezing the form in the meanwhile.

我正在尝试使用线程并防止程序在线程繁忙时冻结。它应该显示进度(写入 0/1),而不是只显示完成后的结果,同时冻结表单。

In the current program I'm trying to write to a textbox, and actually see constant progress, and the form can't be affected by the tasks of the other thread.

在当前程序中,我正在尝试写入文本框,并且实际上看到了不断的进展,并且该表单不会受到另一个线程的任务的影响。

What I have now is I can write to a textbox with a thread using invoke, but It only shows the result (Form freezes while thread is busy), and the form freezes.

我现在拥有的是我可以使用 invoke 用线程写入文本框,但它只显示结果(线程忙时表单冻结),并且表单冻结。

Form image:

表格图片:

enter image description here

在此处输入图片说明

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace MultiThreading
{
public partial class MultiThreading : Form
{
    public MultiThreading()
    {
        InitializeComponent();
    }

    Thread writeOne, writeTwo;

    private void writeText(TextBox textBox, string text)
    {
        if (textBox.InvokeRequired)
        {
            textBox.BeginInvoke((MethodInvoker)delegate()
            {
                for (int i = 0; i < 500; i++)
                {
                    textBox.Text += text;
                }
            });
        }
        else
        {
            for (int i = 0; i < 500; i++)
            {
                textBox.Text += text;
            }
        }
    }
    private void btnWrite1_Click(object sender, EventArgs e)
    {
        writeOne = new Thread(() => writeText(txtOutput1, "0"));
        writeOne.Start();
    }

    private void btnWrite2_Click(object sender, EventArgs e)
    {
        writeTwo = new Thread(() => writeText(txtOutput2, "1"));
        writeTwo.Start();
    }

    private void btnClear1_Click(object sender, EventArgs e)
    {
        txtOutput1.Clear();
    }

    private void btnClear2_Click(object sender, EventArgs e)
    {
        txtOutput2.Clear();
    }

    private void btnWriteBoth_Click(object sender, EventArgs e)
    {
        writeOne = new Thread(() => writeText(txtOutput1, "0"));
        writeTwo = new Thread(() => writeText(txtOutput2, "1"));

        writeOne.Start();
        writeTwo.Start();
    }

    private void btnClearBoth_Click(object sender, EventArgs e)
    {
        txtOutput1.Clear();
        txtOutput2.Clear();
    }
}

}

EDIT:

编辑:

Btw for anyone wondering, I'm new to multithreading and I'm just trying to write a small program to understand the best way to do this.

顺便说一句,对于任何想知道的人,我是多线程的新手,我只是想编写一个小程序来了解执行此操作的最佳方法。

I understand that my previous invoke didn't realy help because I still wasn't giving the form a chance to update, so its getting there.

我知道我之前的调用并没有真正帮助,因为我仍然没有给表单更新的机会,所以它到达那里。

Ok so running 1 thread like this works, but still running multiple threads together, won't update the form till after the thread is done.
I've added a thread.sleep() so I can try and clear while writing, to see if I can still use the form.

好的,像这样运行 1 个线程是有效的,但仍然同时运行多个线程,直到线程完成后才会更新表单。
我添加了一个 thread.sleep() 以便我可以在写作时尝试清除,看看我是否仍然可以使用该表单。

When writing to 1 textbox I can still clear the screen while writing.
But once I use 2 threads, I can't use the form anymore till the thread completes, and gives the output.

写入 1 个文本框时,我仍然可以在写入时清除屏幕。
但是一旦我使用了 2 个线程,我就不能再使用该表单,直到线程完成并给出输出。

private void writeText(TextBox textBox, string text)
    {
        for (int i = 0; i < 500; i++)
        {
            Invoke(new MethodInvoker(() =>
            {
                textBox.Text += text;
                Thread.Sleep(2);
            }));
        }

    }

(If I'm totally wrong on this I don't mind having to read through some examples/threads, I'm still trying to see what is the best way to do this, besides a backgroundworker)

(如果我在这方面完全错了,我不介意阅读一些示例/线程,除了后台工作人员之外,我仍在尝试了解执行此操作的最佳方法是什么)

EDIT 2:

编辑2:

I've reduced the number of invokes by reducing the amount to write, but to increase delay giving the same effect of constant writing, just reducing the load.

我通过减少写入量来减少调用次数,但为了增加延迟,产生与持续写入相同的效果,只是减少了负载。

private void writeText(TextBox textBox, string text)
    {
        for (int i = 0; i < 500; i++)
        {
            Invoke(new MethodInvoker(() =>
            {
                textBox.Text += text;
                Thread.Sleep(2);
            }));
        }

    }

EDIT 3:

编辑 3:

Sumeet's example works using

Sumeet 的示例使用

Application.DoEvents();

Application.DoEvents();

(notice the s, .DoEvent doesn't work, typo probably :P), writing multiple strings simultaneously & having them show the progress and not just the result.

(注意 s,.DoEvent 不起作用,可能是错字:P),同时写入多个字符串并让它们显示进度,而不仅仅是结果。

So Code update again :)

所以代码再次更新:)

*Using a new button to create 5 threads that write a random number to both textboxes

*使用新按钮创建 5 个线程,将随机数写入两个文本框

private void writeText(TextBox textBox, string text)
    {
        for (int i = 0; i < 57; i++)
        {
            Invoke(new MethodInvoker(() =>
            {
                textBox.Text += text;
                Thread.Sleep(5);
                Application.DoEvents();
            }));
        }

    }
private void btnNewThread_Click(object sender, EventArgs e)
    {
        Random random = new Random();
        int[] randomNumber = new int[5];
        for (int i = 0; i < 5; i++)
        {
            randomNumber[i] = random.Next(2, 9);
            new Thread(() => writeText(txtOutput1, randomNumber[i-1].ToString())).Start();
            new Thread(() => writeText(txtOutput2, randomNumber[i-1].ToString())).Start();
        }
    }

采纳答案by sumeet

This solution works ! Have checked it.

这个解决方案有效!检查过了。

The problem is you keep telling the UI thread to change the Text, but never letting it have time to show you the updated text. To make your UI show the changed text, add the Application.DoEventsline like this :

问题是您一直告诉 UI 线程更改文本,但永远不会让它有时间向您显示更新后的文本。要使您的 UI 显示更改的文本,请添加Application.DoEvents行,如下所示:

textBox.Text += text;
Application.DoEvents();

p.s. : Remove the else block of your If / Else loop, it is redundant, and also as pointed by others there is not any use of creating those 2 Threads as all they are doing is post the message on the UI Thread itself.

ps:删除If / Else循环的else块,它是多余的,而且正如其他人指出的那样,创建这两个线程没有任何用处,因为他们所做的只是在UI线程本身上发布消息。

回答by SLaks

You're defeating the purpose of using threads.

你违背了使用线程的目的。

All your thread does is tell the UI threadto execute some code using BeginInvoke().

您的线程所做的就是告诉UI 线程使用BeginInvoke().

All of the actual work happens on the UI thread.

所有实际工作都发生在 UI 线程上。

回答by Sten Petrov

You're still performing a single-threaded task, just re-launching it on the UI thread if needed.

您仍在执行单线程任务,只需在需要时在 UI 线程上重新启动它。

for (int i = 0; i < 500; i++){
    string text = ""+i;
    textBox.BeginInvoke((MethodInvoker)delegate()
            {
                textBox.Text += text;
            });
}

回答by Servy

The problem is that you're starting a new thread, and then that new thread is doing nothing except adding one new task for the UI thread to process that does a lot of work. To keep your form responsive you need to have time where the UI thread is doing nothing, or at least not spending a significant amount of time doing any one task.

问题是您正在启动一个新线程,然后该新线程除了为 UI 线程添加一个新任务以处理大量工作之外什么都不做。为了让你的表单保持响应,你需要有时间让 UI 线程什么都不做,或者至少不要花大量时间做任何一项任务。

To keep the form responsive we need to have lots of little BeginInvoke(or Invoke) calls.

为了保持表单响应,我们需要有很多小的BeginInvoke(或Invoke)调用。

private void writeText(TextBox textBox, string text)
{
    for (int i = 0; i < 500; i++)
    {
        Invoke(new MethodInvoker(() =>
        {
            textBox.Text += text;
        }));
    }
}

By having lots of little invoke calls it allows things like paint events, mouse move/click events, etc. to be handled in the middle of your operations. Also note that I removed the InvokeRequiredcall. We knowthat this method will be called from a non-UI thread, so there's no need for it.

通过有很多小的调用调用,它允许在操作过程中处理诸如绘制事件、鼠标移动/单击事件等。另请注意,我删除了InvokeRequired电话。我们知道这个方法会从非 UI 线程调用,所以没有必要。

回答by RogerN

Either you're doing data processing or you're just trying to animate the UI.

要么您正在进行数据处理,要么只是尝试为 UI 设置动画。

For data processing you should do all the heavy lifting on a background thread and only update the UI occasionally. In your example a TextBox is particularly troublesome in this regard, as you're adding data to the underlying data model several hundred times and the UI element (a TextBox) takes longer to render each time. You must be careful about how often to update the UIso that processing for UI updates does not overwhelm data model updates. TextBoxes are nasty like that.

对于数据处理,您应该在后台线程上完成所有繁重的工作,并且只偶尔更新 UI。在您的示例中,TextBox 在这方面特别麻烦,因为您将数据添加到基础数据模型数百次,而 UI 元素(一个 TextBox)每次都需要更长的时间来呈现。 您必须注意更新 UI 的频率,以便 UI 更新的处理不会压倒数据模型更新。文本框就是这样令人讨厌。

In the example below, a flag set during the paint event ensures that additional UI updates aren't queued until the TextBox has finished painting the last update:

在下面的示例中,在绘制事件期间设置的标志可确保在 TextBox 完成最后一次更新的绘制之前,其他 UI 更新不会排队:

string str = string.Empty;
public void DoStuff()
{
    System.Threading.ThreadPool.QueueUserWorkItem(WorkerThread);
}

void WorkerThread(object unused)
{
    for (int i = 0; i < 1000; i++)
    {
        str += "0";
        if (updatedUI)
        {
            updatedUI = false;
            BeginInvoke(new Action<string>(UpdateUI), str);
        }
    }
    BeginInvoke(new Action<string>(UpdateUI), str);
}

private volatile bool updatedUI = true;
void textbox1_Paint(object sender, PaintEventArgs e) // event hooked up in Form constructor
{
    updatedUI = true;
}

void UpdateUI(string str)
{
    textBox1.Text = str;
}

On the other hand if UI animation is your goal then you probably ought to be using something other than a TextBox. It's just not designed to handle updates so frequently. There might be some optimizations to text rendering you could make for your specific use case.

另一方面,如果您的目标是 UI 动画,那么您可能应该使用 TextBox 以外的其他东西。它只是不适合处理如此频繁的更新。您可以针对特定用例对文本呈现进行一些优化。

回答by Boppity Bop

You must never use a string in high volume applications. UI or not. Multi-threading or not.

绝不能在高容量应用程序中使用字符串。用户界面与否。多线程与否。

You should use StringBuilder to accumulate the string. and then assign

您应该使用 StringBuilder 来累积字符串。然后分配

tb.Text = sb.ToString();