线程基础知识

时间:2011-02-09 10:36:07

标签: c# multithreading

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 testThreads
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        public void countToLots()
        {
            for (int i = 0; i < 10000000; i++)
            {
                textBox1.Text = "Counting to 10000000, value is " + i + Environment.NewLine;
            }
        }

        public void countToZero()
        {
            for (int i = 10000000; i > 0; i--)
            {
                textBox2.Text = "Counting to 0, value is " + i + Environment.NewLine;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Thread countUp = new Thread(new ThreadStart(countToLots));
            Thread countDown = new Thread(new ThreadStart(countToZero));
            countUp.Start();
            countDown.Start();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            textBox3.Text = "Bobby bob bob " + Environment.NewLine;
        }
    }
}

我真的需要尝试并掌握这一点 - 我只是不理解为什么我会收到错误消息的理论。有人可以帮帮我吗?

  

跨线程操作无效:   从a访问控制'textBox1'   线程以外的线程   创建于。

3 个答案:

答案 0 :(得分:5)

UI控件具有“线程亲和力”;他们希望被UI线程以外的任何东西所触动;包括阅读和写作属性。 .Text的分配应该通过UI线程完成,方法是使用InvokeBackgroundWorker

例如:

public void countToLots()
{
    for (int i = 0; i < 10000000; i++)
    {
        // running on bg thread
        textBox1.Invoke((MethodInvoker) delegate {
            // running on UI thread
            textBox1.Text = "Counting to 10000000, value is "
                      + i + Environment.NewLine;
        });
        // running on bg thread again
    }
}

但是注意这种类型的线程切换有开销。你不应该回调每一次迭代 - 你应该(例如)每[n]次迭代更新一次UI - 在上面,例如每10000次。

答案 1 :(得分:2)

您不能在与创建(称为新)控件的线程不同的线程中使用Form控件的方法或属性。

要做到这一点,只需这样做:

    public void countToLots()
    {
        for (int i = 0; i < 10000000; i++)
        {
            SetText("Counting to 10000000, value is " + i + Environment.NewLine);
        }
    }

    public void SetText(string text)
    {

        if (this.textBox1.InvokeRequired())
        {
            Action<string> auxDelegate = SetText;
            this.BeginInvoke(auxDelegate,text);
        }
        else
        {
            this.textBox1.Text = text;
        }
    }

该方法使用beginInvoke执行的操作只是从创建控件的线程再次调用SetText方法。

答案 2 :(得分:1)

好的,关于WHY控件背后的理论有UI线程亲和力。

如果您编程的时间足够长,您会记得表单和快速应用程序开发不是标准的日子。在那些日子里,只需将一个控件放入一个表单中就很少了......一切都是由旧学校完成的。

现在,在Windows中,“旧学校”的做事方式涉及定义WindowProc

WindowProc是一个被调用来处理应用程序消息的函数(注意我说的是,不是)。此函数在主程序线程上运行,负责处理应用程序收到的每条消息,包括用户界面绘制和刷新。

现在所有这些都是自动化的,所以当你创建一个表单时,负责完成所有工作的代码是自动生成的,你不必担心......但它仍然存在。

当然,如果负责用其所有控件绘制用户界面的线程是主线程,那么您将看到如何从另一个线程更改内容可能会因竞争条件等而干扰应用程序本身。此外,由于UI处理是自动生成的,因此您不能只使用两个标准线程使用的同步机制,因为您只能访问一个线程上的代码,而不是主窗口回调。

在某种程度上,BeginInvoke将为您做的是将消息传递给主线程,告诉它在时间正确的时候在她自己的上下文中处理委托,从而将执行委托给主线程。