C# 从另一个线程写入文本框?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/519233/
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-04 06:31:56  来源:igfitidea点击:

Writing to a TextBox from another thread?

c#winformsmultithreading

提问by

I cannot figure out how to make a C# Windows Form application write to a textbox from a thread. For example in the Program.cs we have the standard main() that draws the form:

我无法弄清楚如何使 C# Windows 窗体应用程序从线程写入文本框。例如,在 Program.cs 中,我们有绘制表单的标准 main():

static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new Form1());
}

Then we have in the Form1.cs:

然后我们在 Form1.cs 中有:

public Form1()
{
    InitializeComponent();

    new Thread(SampleFunction).Start();
}

public static void SampleFunction()
{
    while(true)
        WindowsFormsApplication1.Form1.ActiveForm.Text += "hi. ";
}

Am I going about this completely wrong?

我这样做是完全错误的吗?

UPDATE

更新

Here is the working code sample provided from bendewey:

以下是本德威提供的工作代码示例:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        new Thread(SampleFunction).Start();
    }

    public void AppendTextBox(string value)
    {
        if (InvokeRequired)
        {
            this.Invoke(new Action<string>(AppendTextBox), new object[] {value});
            return;
        }
        textBox1.Text += value;
    }

    void SampleFunction()
    {
        // Gets executed on a seperate thread and 
        // doesn't block the UI while sleeping
        for(int i = 0; i<5; i++)
        {
            AppendTextBox("hi.  ");
            Thread.Sleep(1000);
        }
    }
}

采纳答案by bendewey

On your MainForm make a function to set the textbox the checks the InvokeRequired

在您的 MainForm 上创建一个函数来设置文本框,检查 InvokeRequired

public void AppendTextBox(string value)
{
    if (InvokeRequired)
    {
        this.Invoke(new Action<string>(AppendTextBox), new object[] {value});
        return;
    }
    ActiveForm.Text += value;
}

although in your static method you can't just call.

虽然在你的静态方法中你不能只是调用。

WindowsFormsApplication1.Form1.AppendTextBox("hi. ");

you have to have a static reference to the Form1 somewhere, but this isn't really recommended or necessary, can you just make your SampleFunction not static if so then you can just call

你必须在某处有一个对 Form1 的静态引用,但这并不是真正推荐或必要的,你能不能让你的 SampleFunction 不是静态的,如果是这样,你就可以调用

AppendTextBox("hi. ");

It will append on a differnt thread and get marshalled to the UI using the Invoke call if required.

它将附加到不同的线程上,并在需要时使用 Invoke 调用编组到 UI。

Full Sample

完整样本

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        new Thread(SampleFunction).Start();
    }

    public void AppendTextBox(string value)
    {
        if (InvokeRequired)
        {
            this.Invoke(new Action<string>(AppendTextBox), new object[] {value});
            return;
        }
        textBox1.Text += value;
    }

    void SampleFunction()
    {
        // Gets executed on a seperate thread and 
        // doesn't block the UI while sleeping
        for(int i = 0; i<5; i++)
        {
            AppendTextBox("hi.  ");
            Thread.Sleep(1000);
        }
    }
}

回答by Dan C.

Have a look at Control.BeginInvokemethod. The point is to never update UI controls from another thread. BeginInvoke will dispatch the call to the UI thread of the control (in your case, the Form).

看看Control.BeginInvoke方法。重点是永远不要从另一个线程更新 UI 控件。BeginInvoke 会将调用分派到控件的 UI 线程(在您的情况下是窗体)。

To grab the form, remove the static modifier from the sample function and use this.BeginInvoke() as shown in the examples from MSDN.

要获取表单,请从示例函数中删除静态修饰符并使用 this.BeginInvoke(),如 MSDN 中的示例所示。

回答by Dan C.

or you can do like

或者你可以像

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        new Thread( SampleFunction ).Start();
    }

    void SampleFunction()
    {
        // Gets executed on a seperate thread and 
        // doesn't block the UI while sleeping
        for ( int i = 0; i < 5; i++ )
        {
            this.Invoke( ( MethodInvoker )delegate()
            {
                textBox1.Text += "hi";
            } );
            Thread.Sleep( 1000 );
        }
    }
}

回答by Chris KL

What's even easier is to just use the BackgroundWorker control...

更简单的是只使用 BackgroundWorker 控件......

回答by Rinat Abdullin

You need to perform the action from the thread that owns the control.

您需要从拥有控件的线程执行操作。

That's how I'm doing that without adding too much code noise:

这就是我在不添加太多代码噪音的情况下这样做的方式:

control.Invoke(() => textBox1.Text += "hi");

Where Invoke overload is a simple extension from Lokad Shared Libraries:

其中 Invoke 重载是Lokad 共享库的简单扩展:

/// <summary>
/// Invokes the specified <paramref name="action"/> on the thread that owns     
/// the <paramref name="control"/>.</summary>
/// <typeparam name="TControl">type of the control to work with</typeparam>
/// <param name="control">The control to execute action against.</param>
/// <param name="action">The action to on the thread of the control.</param>
public static void Invoke<TControl>(this TControl control, Action action) 
  where TControl : Control
{
  if (!control.InvokeRequired)
  {
    action();
  }
  else
  {
    control.Invoke(action);
  }
}

回答by Mike

I would use BeginInvokeinstead of Invokeas often as possible, unless you are really required to wait until your control has been updated (which in your example is not the case). BeginInvokeposts the delegate on the WinForms message queue and lets the calling code proceed immediately (in your case the for-loop in the SampleFunction). Invokenot only posts the delegate, but also waits until it has been completed.

我会尽可能多地使用BeginInvoke而不是Invoke,除非你真的需要等到你的控件更新(在你的例子中不是这样)。BeginInvoke在 WinForms 消息队列上发布委托并让调用代码立即继续(在您的情况下是 中的 for 循环SampleFunction)。Invoke不仅发布委托,还等待它完成。

So in the method AppendTextBoxfrom your example you would replace Invokewith BeginInvokelike that:

因此,在AppendTextBox您的示例中的方法中,您将替换InvokeBeginInvoke

public void AppendTextBox(string value)
{
    if (InvokeRequired)
    {
        this.BeginInvoke(new Action<string>(AppendTextBox), new object[] {value});
        return;
    }
    textBox1.Text += value;
}

Well and if you want to get even more fancy, there is also the SynchronizationContextclass, which lets you basically do the same as Control.Invoke/Control.BeginInvoke, but with the advantage of not needing a WinForms control reference to be known. Hereis a small tutorial on SynchronizationContext.

好吧,如果您想变得更花哨,还有SynchronizationContext类,它可以让您基本上执行与 相同的操作Control.Invoke/Control.BeginInvoke,但优点是不需要知道 WinForms 控件引用。是一个关于 的小教程SynchronizationContext

回答by marsh-wiggle

Most simple, without caring about delegates

最简单,不关心delegate

if(textBox1.InvokeRequired == true)
    textBox1.Invoke((MethodInvoker)delegate { textBox1.Text = "Invoke was needed";});

else
    textBox1.Text = "Invoke was NOT needed"; 

回答by Iqra.

Here is the what I have done to avoid CrossThreadExceptionand writing to the textbox from another thread.

这是我为避免CrossThreadException和从另一个线程写入文本框所做的工作。

Here is my Button.Clickfunction- I want to generate a random number of threads and then get their IDsby calling the getID()method and the TextBox value while being in that worker thread.

这是我的Button.Click函数 - 我想生成随机数量的线程,然后IDs通过getID()在该工作线程中调用方法和 TextBox 值来获取它们。

private void btnAppend_Click(object sender, EventArgs e) 
{
    Random n = new Random();

    for (int i = 0; i < n.Next(1,5); i++)
    {
        label2.Text = "UI Id" + ((Thread.CurrentThread.ManagedThreadId).ToString());
        Thread t = new Thread(getId);
        t.Start();
    }
}

Here is getId(workerThread) code:

这是getId(workerThread)代码:

public void getId()
{
    int id = Thread.CurrentThread.ManagedThreadId;
    //Note that, I have collected threadId just before calling this.Invoke
    //method else it would be same as of UI thread inside the below code block 
    this.Invoke((MethodInvoker)delegate ()
    {
        inpTxt.Text += "My id is" +"--"+id+Environment.NewLine; 
    });
}