1

我复制了以下示例Microsoft Thread 示例

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

可能是什么问题?谢谢达摩

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

4 回答 4

2

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

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

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

  1. 直接使用同步上下文:

.

// 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

.

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

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

于 2012-11-29T21:13:58.183 回答
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);
            }
        }


    }

快乐编码!:)

于 2012-11-29T21:03:02.850 回答
1

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

后台线程必须使用Control.Invoke(Delegate)切换到主 UI 线程才能访问 Controls。然后,当 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;
}
于 2012-11-29T21:04:22.767 回答
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;

        }
于 2012-11-29T21:17:48.553 回答