首页 文章

InvalidOperationException:调用线程无法访问此对象,因为另一个线程拥有它 . [重复]

提问于
浏览
4

可能重复:调用线程无法访问此对象,因为另一个线程拥有它

错误:

The calling thread cannot access this object because a different thread owns it.

码:

public partial class MainWindow : Window
    {
        Thread t;
        bool interrupt;
        public MainWindow()
        {
            InitializeComponent();
        }

        private void btss_Click(object sender, RoutedEventArgs e)
        {
            if (t == null)
            {
                t = new Thread(this.calculate);
                t.Start();
                btss.Content = "Stop";
            }
            else
            {
                t.Interrupt();
            }

        }

        private void calculate()
        {
            int currval = 2;
            int devide = 2;
            while (!interrupt)
            {
                for (int i = 2; i < currval/2; i++)
                {
                    if (2 % i != 0)
                    {
                        lbPrimes.Items.Add(currval.ToString()); //Error occures here
                    }
                }
                currval++;
            }
        }
    }

是什么导致这种情况,我该如何解决?

4 回答

  • 0

    您需要重新加入主UI线程才能影响UI . 您可以使用InvokeRequired检查是否需要这样做,并在引用控件之前实现Invoke .

    private void calculate()
    {
        if (InvokeRequired)
        {
            Invoke(new Action(() => calculate()));
        }
        else
        {
          //
        }
     }
    
  • 4

    不允许从非UI线程访问任何UI元素(此处为 lblPrimes ) . 您必须使用线程中的 Invoke 来执行此操作 .

    这是一个很好的教程:

    http://weblogs.asp.net/justin_rogers/pages/126345.aspx

  • 2

    您只能从主线程更新GUI .

    在您的worker方法(calculate())中,您尝试将项目添加到列表框中 .

    lbPrimes.Items.Add(currval.ToString());
    

    这会导致异常 .

    您正在以非线程安全的方式访问控件 . 当没有创建控件的线程试图调用它时,您将获得InvalidOperationException .

    如果要将项添加到列表框,则需要使用InvokeRequired作为TheCodeKing提及 .

    例如:

    private delegate void AddListItem(string item);
    
    private void AddListBoxItem(string item)
    {
        if (this.lbPrimes.InvokeRequired)
        {
            AddListItem d = new AddListItem(item);
            this.Invoke(d, new object[] { item});
        }
        else
        {
            this.lbPrimes.Items.Add(item);
        }
    }
    

    在Calculate()方法中调用此AddListBoxItem(...)方法,而不是直接尝试将项添加到列表框控件 .

  • 1

    问题是您的工作线程正在尝试访问不允许的UI元素 . 您获得的例外情况是警告您 . 很多时候你甚至都没有 . 相反,您的应用程序将无法预测和惊人地失败 .

    您可以使用 Control.Invoke 将委托的执行封送到UI线程上 . 该代理将执行 lbPrimes.Items.Add 操作 . 但是,在这种情况下我不推荐这种方法 . 原因是它会减慢工作线程的速度 .

    我首选的解决方案是让工作线程将 currval 添加到 ConcurrentQueue . 然后UI线程将通过 System.Windows.Forms.Timer 定期轮询此集合以使值出列并将它们放在 ListBox 中 . 与使用 Control.Invoke 相比,这有很多优点 .

    • 它删除了 Invoke 强加的worker和UI线程之间的紧密耦合 .

    • 它将更新UI的责任放在它应该属于的UI线程中 .

    • UI线程决定更新发生的时间和频率 .

    • 工作线程不必等待UI响应 Invoke 请求 . 它将增加工作线程的吞吐量 .

    • 由于 Invoke 操作成本高,效率更高 .

    • 尝试使用 Invoke 终止工作线程时出现的许多微妙的竞争条件自然消失了 .

    以下是我首选的选项 .

    private void calculate()
    {
      int currval = 2;
      int devide = 2;
      while (!interrupt)
      {
        for (int i = 2; i < currval/2; i++)
        {
          if (2 % i != 0)
          {
            queue.Add(currval); // ConcurrentQueue<int>
          }
        }
        currval++;
      }
    }
    
    private void Timer_Tick(object sender, EventArgs args)
    {
      int value;
      while (queue.TryDequeue(out value))
      {
        lbPrimes.Items.Add(value.ToString());
      }
    }
    

    我注意到了其他一些问题 .

    • Thread.Interrupt 取消阻止BCL等待调用,如 WaitOneJoinSleep 等 . 您对它的使用没有任何意义 . 我想你要做的是设置 interrupt = true .

    • 你应该在 for 循环中 interrupt 而不是 while 循环 . 如果 currval 变得足够大,则线程需要更长的时间来响应中断请求 .

相关问题