简单的线程问题

时间:2012-11-29 20:55:19

标签: c# multithreading visual-studio-2010 service

我复制了以下示例Microsoft Thread Example

下面给出了代码 但我在“this.progressBar1.Value = newval;”一行上收到错误声明“跨线程操作无效:控制'progressBar1'从其创建的线程以外的线程访问。”

可能是什么问题? 谢谢 DAMO

C#代码

public partial class Form1 : Form
{         
    public Form1()
    {
        InitializeComponent();
    }
    private void Form1_Load(object sender, EventArgs e)
    {
        Thread trd = new Thread(new ThreadStart(this.ThreadTask));
        trd.IsBackground = true;
        trd.Start();
    }
    private void ThreadTask()
    {
        int stp;
        int newval;
        Random rnd = new Random();
        while (true)
        {
            stp = this.progressBar1.Step * rnd.Next(-1, 2);
            newval = this.progressBar1.Value + stp;
            if (newval > this.progressBar1.Maximum)
                newval = this.progressBar1.Maximum;
            else if (newval < this.progressBar1.Minimum)
                newval = this.progressBar1.Minimum;
            this.progressBar1.Value = newval;
            Thread.Sleep(100);
        }
    }
    private void button1_Click(object sender, EventArgs e)
    {
        MessageBox.Show("This is the main thread");
    }
}

4 个答案:

答案 0 :(得分:2)

首先,我强烈建议使用一些更高级别的技术,例如Tasks,而不是直接使用Thread类。任务类不仅更容易使用,更有效,更容易编写,更容易避免您最近遇到的问题。

您尝试从非UI线程更新UI对象的代码的主要问题。 UI技术(如Windows窗体或WPF)要求只有创建UI对象的线程才能访问其属性。

要解决此问题,您应该将控制从非UI线程封送到UI线程。并且有很多选项可以做到这一点(但是所有这些选项只是一个名为SynchronizationContext概念的语法糖):

  1. 直接使用同步上下文:
  2. // storing SynchronizationContext in the private field of your form
    private SynchronizationContext _syncContext = SyncrhonizationContext.Current;
    
    private void MethodFromTheSeparateThread()
    {
      // Marshaling control to UI thread
      _syncContext.Post(d =>
                {
                    // Put all your code that access UI elements here
                }, null);
    }
    
    1. 使用InvokeRequired / Invoke正如Gregor所提到的那样

    2. 使用TaskScheduler.FromSynchronizationContext

    3. private void ImplementLongRunningOperation()
      {
        int id;
        string name;
        Task.Factory.StartNew(() =>
        {
          // our long-runing part. Getting id and name
          id = 42;
          name = "Jonh Doe";
        }).ContinueWith(t =>
          {
             // Handling results from the previous task.
             // This callback would be called in UI thread!
             label1.Text = id.ToString();
             label2.Text = name;
          }, TaskScheduler.FromSynchronizationContext);
      }
      

      正如我所提到的,如果您正在使用.NET 4.0+,那么最后一种方法(使用Tasks)是一种更好的方法。这不仅可以使您从一些低级别类中省去,而且还可以实现更清晰的设计,因为您可以清楚地分离单独的步骤,如获取数据和处理它们。

答案 1 :(得分:1)

您必须调用新的委托:

    delegate void ThreadTaskDelegate();
    private void ThreadTask()
    {
        if (this.InvokeRequired)
        {
            ThreadTaskDelegate del = new ThreadTaskDelegate(ThreadTask);
            this.Invoke(del, null);
        }
        else
        {
            int stp;
            int newval;
            Random rnd = new Random();
            while (true)
            {
                stp = this.progressBar1.Step * rnd.Next(-1, 2);
                newval = this.progressBar1.Value + stp;

                if (newval > this.progressBar1.Maximum)
                    newval = this.progressBar1.Maximum;
                else if (newval < this.progressBar1.Minimum)
                    newval = this.progressBar1.Minimum;

                this.progressBar1.Value = newval;

                Thread.Sleep(100);
            }
        }


    }

快乐的编码! :)

答案 2 :(得分:1)

这个例子很糟糕。您必须访问创建它们的线程中的控件。这几乎总是主要的UI线程。 (可以为不同的表单创建单独的UI线程,每个表单都有自己的消息泵。但是现在不要担心。)

后台线程在访问Controls之前必须使用Control.Invoke(Delegate)更改为主UI线程。然后,当UI工作完成后,尽快离开UI线程。

例如:

private void ThreadTask()
{
    // This code runs in the background thread.
    while (true)
    {
        if (this.InvokeRequired)
        {
            // In order to access the UI controls, we must Invoke back to the UI thread
            this.Invoke(new ThreadStart(SetRandomProgress));
        }
        else
        {
            // We are already in the UI thread, so we don't have to Invoke
            SetRandomProgress();
        }

        // Wait briefly.  This wait happens in the background thread.
        // During this time, the UI is still responsive, because it is not blocked.
        // You can verify this by tweaking the duration to something longer (say, 5000 ms).
        Thread.Sleep(100);
    }
}

private void SetRandomProgress()
{
    Random rnd = new Random();
    int stp = this.progressBar1.Step * rnd.Next(-1, 2);
    int newval = this.progressBar1.Value + stp;
    if (newval > this.progressBar1.Maximum)
        newval = this.progressBar1.Maximum;
    else if (newval < this.progressBar1.Minimum)
        newval = this.progressBar1.Minimum;

    this.progressBar1.Value = newval;
}

答案 3 :(得分:1)

您可以像这样重写代码,您的progressBar将在UI线程中更新,调用通过委托访问progressBar的方法。检查代码:

private void Form1_Load(object sender, EventArgs e)
        {
            Thread trd = new Thread(new ThreadStart(this.ThreadTask));
            trd.IsBackground = true;
            trd.Start();
        }
        private void ThreadTask()
        {
            Random rnd = new Random();
            while (true)
            {
                int randValue = rnd.Next(-1, 2);
                progressBar1.Invoke(new updater(UpdateProgressBar), new object[] {randValue});
                Thread.Sleep(100);
            }
        }
        private delegate void updater(int value);
        private void UpdateProgressBar(int randValue)
        {
            int stp = this.progressBar1.Step * randValue;
            int newval = this.progressBar1.Value + stp;
            if (newval > this.progressBar1.Maximum)
                newval = this.progressBar1.Maximum;
            else if (newval < this.progressBar1.Minimum)
                newval = this.progressBar1.Minimum;
            this.progressBar1.Value = newval;

        }