【问题标题】:Cross-thread operation not valid: Control 'textbox' accessed from a thread other than the thread it was created on跨线程操作无效:控件“文本框”从创建它的线程以外的线程访问
【发布时间】:2017-05-02 14:53:57
【问题描述】:

我需要一些帮助。我开始使用 c#,但对事件处理和线程还不是很熟悉。作为一个初学者,随着时间的推移和曝光的进展,我想了解更多关于这些高级主题并进行改进的信息,希望大家可以帮助我。

我遇到了“跨线程操作无效:控件'textbox control called stackStatus'从创建它的线程以外的线程访问”的问题。我一整天都试图排除故障,但根本无济于事。我被困住了。 :-( 程序遇到异常,无法继续顺利执行。

我已阅读以下主题并尝试了一些东西,但我想我仍然缺少一些东西。感谢有人可以在这里帮助我。谢谢。

Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on

Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on

以下是大部分代码:

  private void createCloud_Click(object sender, EventArgs e)
    {
        CreateCloud(); //start creation method
        stackStatus.Text = "Creating stack..."; //updates the cloud status textbox
        stackStatus.Refresh();
        Cursor.Current = Cursors.WaitCursor; //change the cursor to wait state
        Start_Describestack(); //call describe method to find out the status of cloud creation progress

        Task.Delay(12000); // wait 12s in case not ready

        Start_Describestack(); // call again describe method to find out the cloud creation progress status
        Cursor.Current = Cursors.Default; //put cursor on wait
        describeevents(); // call method to get all cloud creation event data and publish on the datagridview

    }



    private void Start_Describestack()
    {
        //method making use of timer to call 

        _timer = new System.Timers.Timer(15000);
        _timer.Elapsed += new ElapsedEventHandler(describeStack);
        _timer.Enabled = true;


    }

    delegate void describeStackCallBack(object sender, ElapsedEventArgs e);

    private void describeStack(object sender, ElapsedEventArgs e)
    {
        //this method makes api calls through cloudclient to describe the stack
        //this is where the "Cross-thread operation not valid: Control 'stackStatus' accessed from a thread other than the thread it was created on"


        var client = new cloudclient();
        var request2 = new StacksRequest();

        request2.Cloudstackname = stackid;

        try
        {
            var response = client.DescribeCloudStacks(request2);

            foreach (var stack in response.Stacks)
            {

            //something is wrong here but I do not know how to fix it. Please help
                if (this.stackStatus.InvokeRequired)
                {
                    describeStackCallBack d = new describeStackCallBack(describeStack);
                    this.Invoke(d, new object[] { sender, e });
                    stackStatus.Refresh();
                    describevents();
                }
                else
                {
                    stackStatus.Text = stack.StackStatus;
                    stackStatus.Refresh();
                    describeevents();
                }
            }
        }
        catch (Exception)
        {

            if (this.stackStatus.InvokeRequired)
            {
                describeStackCallBack d = new describeStackCallBack(describeStack);
                this.Invoke(d, new object[] { sender, e });

                stackStatus.Text = "Stack not found/Deleted";
            }
            else
            { stackStatus.Text = "Stack not found/Deleted"; }
        }

     describeevents();
    }

    private void describeevents()
    {


        var newclient = new cloudclient();
        var request3 = new eventrequest();

        request3.Cloudstackname = stackid;


            try
            {
                var response = newclient.eventstack(request3);
                dataGridView3.Rows.Clear();

                foreach (var events in response.sevents)
                {
                    dataGridView3.Rows.Add(events.Timestamp, events.ResourceStatus, events.ResourceType);
                }
            }
            catch (Exception)
            {
                dataGridView3.Rows.Clear();
                MessageBox.Show("Stack not ready!");
            }

        dataGridView3.Refresh();

    }

【问题讨论】:

    标签: c# multithreading timer delegates cloud


    【解决方案1】:

    而不是做:

    stackStatus.Text = "some text";
    

    试试:

    stackStatus.Invoke((Action)delegate
    {
        stackStatus.Text = "some text";
    });
    

    请注意,不推荐使用线程外的 GUI 元素分配或声明它们,因为这些控件可能随时不再可用。

    【讨论】:

    • 谢谢,奈德!它至少现在正在工作。它正在打异常。我需要把它放在尝试部分。但我不明白为什么我的计时器一直在响。还是有问题。实际上我希望计时器调用 describeStack() 一次,但我需要调用两次。
    • 现在,我得想办法把定时器用完后去掉。谢谢。
    • Nadye,因为您提到在线程外分配 GUI 元素或声明它们已被弃用,因为控件可能随时不再可用,我应该如何正常和正确地重组代码?请指教。我喜欢学习。谢谢。
    • 实际上,即使使用上面的代码片段,我仍然会收到“跨线程操作无效:从创建它的线程以外的线程访问的控件'stackStatus'”异常。它没有修复它。
    【解决方案2】:

    您的方法中有两个问题,它们共同阻止您尝试模仿异常解决方案的工作:

    1. 您没有注意到建议的解决方案会调用自身,这样做会导致每次从工作线程调用 foreach 时都会重新启动它。
    2. 您正在关注基于 Invoke() 的跨线程友好代码的 Microsoft 规范实现,恕我直言,这是蹩脚的。

    我认为检查InvokeRequired 是没有意义的。标准模式总是涉及以下情况:在第一次输入时,您知道您将需要Invoke(),即使您没有,在不需要时调用Invoke() 也没有真正的危害。

    相反,您应该始终将应该在 UI 线程中运行的代码和不应该在 UI 线程中运行的代码分开。然后,在不存在的代码中,总是使用Invoke() 来执行存在的代码。

    例如:

    private void Start_Describestack()
    {
        //method making use of timer to call 
        _timer = new System.Timers.Timer(15000);
        _timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
        _timer.Enabled = true;
    }
    
    private void _timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        Invoke((MethodInvoker)describeStack);
    }
    
    private void describeStack()
    {
        var client = new cloudclient();
        var request2 = new StacksRequest();
    
        request2.Cloudstackname = stackid;
    
        try
        {
            var response = client.DescribeCloudStacks(request2);
    
            foreach (var stack in response.Stacks)
            {
                stackStatus.Text = stack.StackStatus;
                stackStatus.Refresh();
                describeevents();
            }
        }
        catch (Exception)
        {
            stackStatus.Text = "Stack not found/Deleted";
        }
    
        describeevents();
    }
    

    也就是说,对上述内容的改进是使用System.Windows.Forms.Timer 而不是System.Timers.Timer。后者在工作线程上引发 Elapsed 事件,但前者在 UI 线程上引发它的事件,就在你想要的地方。根本不需要Invoke()

    您的代码至少还有一个其他问题:

    private void createCloud_Click(object sender, EventArgs e)
    {
        CreateCloud(); //start creation method
        stackStatus.Text = "Creating stack..."; //updates the cloud status textbox
        stackStatus.Refresh();
        Cursor.Current = Cursors.WaitCursor; //change the cursor to wait state
        Start_Describestack(); //call describe method to find out the status of cloud creation progress
    
        Task.Delay(12000); // wait 12s in case not ready
    
        Start_Describestack(); // call again describe method to find out the cloud creation progress status
        Cursor.Current = Cursors.Default; //put cursor on wait
        describeevents(); // call method to get all cloud creation event data and publish on the datagridview
    }
    

    在上面,对Task.Delay(12000); 的调用什么也没做。 Task.Delay() 方法实际上并没有阻塞当前线程。相反,它返回一个等待的任务对象。如果您等待返回的对象,则仅显示它的代码会延迟。

    调用Start_Describestack() 两次也是有问题的,因为这个方法除了启动定时器什么都不做。调用它两次意味着现在你有两个计时器在运行。

    最后,您也不应该在代码中对Refresh() 进行所有这些调用。正确编写的 Windows 窗体代码不需要这样的东西。控件属性的更新会自动导致控件失效,并且控件会在下次有机会时根据需要进行更新,只要代码编写正确,用户很快就不会注意到任何明显的延迟。

    现在,将以上所有内容放在一起,在我看来,您应该完全避免使用计时器。仍然存在一个潜在问题,即您对DescribeCloudStacks() 的调用时间很长,并且可能导致用户界面暂时出现“卡住”,这显然不是一件好事。此外,无论您是否需要Invoke(),基于计时器的代码都可能更难理解,尤其是对于异步编程和线程的新手而言。

    使用async/await 功能,您可以以传统的程序化方式编写代码,同时仍确保 UI 保持响应性,并且与 UI 相关的代码始终在 UI 线程中执行它属于。这可能看起来像这样:

    private async void createCloud_Click(object sender, EventArgs e)
    {
        CreateCloud(); //start creation method
        stackStatus.Text = "Creating stack..."; //updates the cloud status textbox
        Cursor.Current = Cursors.WaitCursor; //change the cursor to wait state
        await describeStack(); //call describe method to find out the status of cloud creation progress
    
        await Task.Delay(12000); // wait 12s in case not ready
    
        await describeStack(); // call again describe method to find out the cloud creation progress status
        Cursor.Current = Cursors.Default; //put cursor on wait
        describeevents(); // call method to get all cloud creation event data and publish on the datagridview
    }
    
    private async Task describeStack()
    {
        var client = new cloudclient();
        var request2 = new StacksRequest();
    
        request2.Cloudstackname = stackid;
    
        try
        {
            var response = await Task.Run(() => client.DescribeCloudStacks(request2));
    
            foreach (var stack in response.Stacks)
            {
                stackStatus.Text = stack.StackStatus;
                describeevents();
            }
        }
        catch (Exception)
        {
            stackStatus.Text = "Stack not found/Deleted";
        }
    
        describeevents();
    }
    

    上面在UI线程中执行了大部分describeStacks()方法。例外情况是 DescribeCloudStacks() 方法调用,它作为工作任务运行。在运行时,UI 线程可以正常运行。 describeStacks() 方法的执行在工作任务运行时被暂时“搁置”(阻塞 UI 线程),然后在完成时恢复。

    从您的原始示例中不清楚您是否真的想要一个重复计时器。以上不使用任何循环;它只调用了两次describeStack() 方法,中间有12 秒的延迟。但是如果你想要一个循环,你也可以这样做。只需将await Task.Delay() 用于延迟,await describeStack() 用于操作,然后根据需要将其放入循环中。

    【讨论】:

      【解决方案3】:

      我没有看到 stackStatus 对象是在哪里创建的,所以我只是猜测您正在通过包含 describeStack() 的类的构造函数创建它,然后您正在为点击注册一个事件处理程序。我认为正在发生的事情是事件处理程序正在与创建实例的线程不同的线程上运行,因此您可能必须更改创建stackStatus 对象的方式。该错误很可能发生,因为无论从哪个类型创建 stackStatus 都已知是不可重入的,因此当运行时检测到线程之间的访问时,它会引发异常,以便您了解并可以防止或从竞争条件或死锁中恢复。

      【讨论】:

      • 嗨,Kelly,stackStatus 只是我在 Winform 中制作的一个文本框。我使用 describeStack() 来获取此文本框的状态和更新值。对不起,我忘了提这个。
      猜你喜欢
      • 2011-01-15
      • 2016-05-21
      • 2016-06-11
      • 2015-05-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多