【问题标题】:Writing to a TextBox from another thread? [duplicate]从另一个线程写入文本框? [复制]
【发布时间】:2010-10-05 21:01:36
【问题描述】:

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

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

然后我们在Form1.cs中有:

public Form1()
{
    InitializeComponent();

    new Thread(SampleFunction).Start();
}

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

我这样做完全错了吗?

更新

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

【问题讨论】:

    标签: c# winforms multithreading


    【解决方案1】:

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

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

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

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

    您必须在某处对 Form1 进行静态引用,但这并不是真正推荐或不必要的,如果是这样,您可以让您的 SampleFunction 不是静态的,那么您可以调用

    AppendTextBox("hi. ");
    

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

    完整样本

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

    【讨论】:

    • 我尝试了您的代码,但它对我来说不太适用。我很感激回答伙伴。
    • 更具体地说,它会编译,但不会连续写入 TextBox。
    • 在您的代码中,您取出 while(true) 将其放回您的示例函数中,您应该会摇摆不定。不过会写很多。如果您尝试测试响应能力,您可能需要放置 Thread.Sleep(1000) 1 秒
    • 本德威,你这个摇滚伙伴!我可能花了 2 个小时把头撞在显示器上。
    【解决方案2】:

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

    因此,在您的示例中的方法 AppendTextBox 中,您可以像这样将 Invoke 替换为 BeginInvoke

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

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

    【讨论】:

    • 谢谢!我不知道BeginInvokeInvoke 之间的区别。仅此一项就解决了我的问题(并且可能在未来帮助了我很多,因为BeginInvoke 不会导致等待)
    • 我一直在尝试接受的答案,但没有结果,我的应用程序总是挂起。多亏了这个很好的解释,我终于可以再次入睡了
    【解决方案3】:

    或者你可以这样做

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

    【讨论】:

      【解决方案4】:

      很简单,不用担心委托:

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

      【讨论】:

        【解决方案5】:

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

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

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

        Invoke 重载是Lokad Shared Libraries 的简单扩展:

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

        【讨论】:

          【解决方案6】:

          看看Control.BeginInvoke 方法。关键是永远不要从另一个线程更新 UI 控件。 BeginInvoke 会将调用分派给控件的 UI 线程(在您的情况下为 Form)。

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

          【讨论】:

            【解决方案7】:

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

            【讨论】:

              【解决方案8】:

              这是我为避免CrossThreadException 并从另一个线程写入文本框而采取的措施。

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

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

              这里是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; 
                  });
              }
              

              【讨论】:

                猜你喜欢
                • 2018-03-10
                • 2021-01-31
                • 2012-04-19
                • 1970-01-01
                • 1970-01-01
                • 2014-07-07
                • 1970-01-01
                • 2017-07-17
                相关资源
                最近更新 更多