【问题标题】:How to run a GUI form in another thread?如何在另一个线程中运行 GUI 表单?
【发布时间】:2013-08-21 00:45:03
【问题描述】:

请看下面的代码

#pragma once


    using namespace System::ComponentModel;
    using namespace System::Collections;
    using namespace System::Windows::Forms;
    using namespace System::Data;
    using namespace System::Threading;

    /// <summary>
    /// Summary for NotifyAlarm
    /// </summary>
    public ref class NotifyAlarm : public System::Windows::Forms::Form
    {
        int count;


    public:
        NotifyAlarm(void)
        {
            InitializeComponent();
            //
            //TODO: Add the constructor code here
            //
            count = 10;
        }

    protected:
        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        ~NotifyAlarm()
        {
            if (components)
            {
                delete components;
            }
        }
    private: System::Windows::Forms::Label^  label1;
    protected: 
    private: System::Windows::Forms::Label^  secondsLabel;
    private: System::Windows::Forms::Label^  label2;
    private: System::Windows::Forms::Button^  sendNowBtn;
    private: System::Windows::Forms::Button^  cancelBtn;
    private: System::Windows::Forms::Timer^  timer1;
    private: System::ComponentModel::IContainer^  components;


    private:
        /// <summary>
        /// Required designer variable.
        /// </summary>


#pragma region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        void InitializeComponent(void)
        {
            this->components = (gcnew System::ComponentModel::Container());
            this->label1 = (gcnew System::Windows::Forms::Label());
            this->secondsLabel = (gcnew System::Windows::Forms::Label());
            this->label2 = (gcnew System::Windows::Forms::Label());
            this->sendNowBtn = (gcnew System::Windows::Forms::Button());
            this->cancelBtn = (gcnew System::Windows::Forms::Button());
            this->timer1 = (gcnew System::Windows::Forms::Timer(this->components));
            this->SuspendLayout();
            // 
            // label1
            // 
            this->label1->AutoSize = true;
            this->label1->Font = (gcnew System::Drawing::Font(L"Microsoft Sans Serif", 12, System::Drawing::FontStyle::Regular, System::Drawing::GraphicsUnit::Point, 
                static_cast<System::Byte>(0)));
            this->label1->Location = System::Drawing::Point(13, 27);
            this->label1->Name = L"label1";
            this->label1->Size = System::Drawing::Size(405, 20);
            this->label1->TabIndex = 0;
            this->label1->Text = L"Intruder Detected. An Email and SMS will be sent within ";
            // 
            // secondsLabel
            // 
            this->secondsLabel->AutoSize = true;
            this->secondsLabel->Font = (gcnew System::Drawing::Font(L"Microsoft Sans Serif", 12, System::Drawing::FontStyle::Regular, System::Drawing::GraphicsUnit::Point, 
                static_cast<System::Byte>(0)));
            this->secondsLabel->ForeColor = System::Drawing::Color::Red;
            this->secondsLabel->Location = System::Drawing::Point(408, 27);
            this->secondsLabel->Name = L"secondsLabel";
            this->secondsLabel->Size = System::Drawing::Size(51, 20);
            this->secondsLabel->TabIndex = 1;
            this->secondsLabel->Text = L"label2";
            // 
            // label2
            // 
            this->label2->AutoSize = true;
            this->label2->Font = (gcnew System::Drawing::Font(L"Microsoft Sans Serif", 12, System::Drawing::FontStyle::Regular, System::Drawing::GraphicsUnit::Point, 
                static_cast<System::Byte>(0)));
            this->label2->Location = System::Drawing::Point(465, 27);
            this->label2->Name = L"label2";
            this->label2->Size = System::Drawing::Size(69, 20);
            this->label2->TabIndex = 2;
            this->label2->Text = L"seconds";
            // 
            // sendNowBtn
            // 
            this->sendNowBtn->Location = System::Drawing::Point(370, 70);
            this->sendNowBtn->Name = L"sendNowBtn";
            this->sendNowBtn->Size = System::Drawing::Size(75, 23);
            this->sendNowBtn->TabIndex = 3;
            this->sendNowBtn->Text = L"Send Now";
            this->sendNowBtn->UseVisualStyleBackColor = true;
            this->sendNowBtn->Click += gcnew System::EventHandler(this, &NotifyAlarm::sendNowBtn_Click);
            // 
            // cancelBtn
            // 
            this->cancelBtn->Location = System::Drawing::Point(469, 70);
            this->cancelBtn->Name = L"cancelBtn";
            this->cancelBtn->Size = System::Drawing::Size(75, 23);
            this->cancelBtn->TabIndex = 4;
            this->cancelBtn->Text = L"Cancel";
            this->cancelBtn->UseVisualStyleBackColor = true;
            this->cancelBtn->Click += gcnew System::EventHandler(this, &NotifyAlarm::cancelBtn_Click);
            // 
            // timer1
            // 
            this->timer1->Enabled = true;
            this->timer1->Interval = 1000;
            this->timer1->Tick += gcnew System::EventHandler(this, &NotifyAlarm::timer1_Tick);
            // 
            // NotifyAlarm
            // 
            this->AutoScaleDimensions = System::Drawing::SizeF(6, 13);
            this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
            this->ClientSize = System::Drawing::Size(566, 105);
            this->Controls->Add(this->cancelBtn);
            this->Controls->Add(this->sendNowBtn);
            this->Controls->Add(this->label2);
            this->Controls->Add(this->secondsLabel);
            this->Controls->Add(this->label1);
            this->Name = L"NotifyAlarm";
            this->Text = L"NotifyAlarm";
            this->ResumeLayout(false);
            this->PerformLayout();

        }
#pragma endregion
    private: System::Void timer1_Tick(System::Object^  sender, System::EventArgs^  e) 
             {
                 count--;

                 if(count>0 || count==0)
                 {
                     secondsLabel->Text = ""+count;
                 } 
                 else
                 {
                     //code removed
                 }
             }
private: System::Void sendNowBtn_Click(System::Object^  sender, System::EventArgs^  e) 
         {
             timer1->Stop();

            //code removed
         }

public: System::Void showGUI() 
                  {
                      this->Show();
                  }



};

这是一种通知框,我试图在新的线程中打开它。原因是,默认线程已经超载。这就是我从另一个线程调用它的方式

na = gcnew NotifyAlarm();
        Thread ^alertThread = gcnew Thread(gcnew System::Threading::ThreadStart(na,&NotifyAlarm::showGUI));
        alertThread->Start();

不幸的是,当我运行此线程时,我收到以下错误

当代码到达这里时发生这种情况

 if(count>0 || count==0)
  {
        secondsLabel->Text = ""+count;
  } 

如您所见,我正在尝试更新其中的标签。

那么,我怎样才能让这个 GUI 表单在另一个线程中运行而不会出现这些错误?

PS:我不是来自 .NET 文化,而是来自 Java 和 C++。

【问题讨论】:

    标签: .net winforms visual-studio-2010 visual-c++ c++-cli


    【解决方案1】:

    您应该使用BeginInvoke 来同步(调度)对 UI 线程的调用:

    delegate void UpdateTextDelegate(int count);
    
    private: void DoUpdateText(int count)
    {
        ISynchronizeInvoke^ i = this;
    
        if (i->InvokeRequired)
        {
          UpdateTextDelegate^ tempDelegate =
            gcnew UpdateTextDelegate(this, &Form1::DoUpdateText);
          cli::array<System::Object^>^ args = gcnew cli::array<System::Object^>(1);
          args[0] = count;
          i->BeginInvoke(tempDelegate, args);
          return;
        }
    
        secondsLabel->Text = count.ToString();
    }
    

    然后你可以在另一个线程中调用DoUpdateText 方法。

    【讨论】:

    • 是的,这似乎就是答案。谢谢你:)
    【解决方案2】:

    目前尚不清楚 Text 属性分配发生在哪个线程上。但显然是在错误的线程上,您需要使用表单或标签的 BeginInvoke() 方法来编组调用。

    请注意,在启动线程之前创建表单对象实例是有风险的。规则是实际创建窗口句柄(CreateHandle() 调用)的任何线程都是窗口的所有者。如果表单的构造函数意外创建了句柄,那 可能 是错误的线程。只需在线程方法上创建表单对象即可避免麻烦。

    值得注意的是,这是多么危险。一个重要的麻烦制造者是 SystemEvents 类,许多控件订阅了 UserPreferenceChanged 事件。当用户更改主题设置时,他们这样做是为了重新绘制自己。在其他情况下也会触发此事件,锁定工作站是一个臭名昭著的麻烦来源。桌面开关可以触发事件。

    SystemEvents 具有在正确线程上触发此事件的令人羡慕的任务。它不能,你给它两个线程可供选择。您的主 UI 线程和这个新的“gui 线程”。其中之一将在错误的线程上触发事件。结果是死锁。它可能在您的表单关闭并且线程不再存在之后很长时间发生。

    这很难处理。还有更多问题,窗口有自己的生命,并且与您应用程序中的其余窗口没有 Z 顺序关系。一个重要的问题是它习惯于在主线程拥有的窗口下方显示自己。用户看不到。这种问题需要通过使其成为一个拥有的窗口来解决,这样才能保证它是顶部的。这也不起作用,当您调用 Show(owner) 重载时,您将再次获得 InvalidOperationException。

    非常讨厌的问题,信息应该很清楚:不要这样做。永远不需要,程序的主线程可以处理任意数量的窗口。典型的错误是使用这样的通知窗口来隐藏在主线程上运行的代码中的缺陷并且花费太多时间,从而使 GUI 无响应。真正的解决方法是在工作线程上运行 代码。

    【讨论】:

    • 非常感谢您的解释。太好了。我的主线程超载不是因为窗口,而是因为该线程中正在发生语音识别
    • 非常感谢您的解释。太好了。我的主线程超载不是因为窗口,而是因为该线程中正在发生语音识别。这是一个人工智能软件,所以由于一些未知的原因,如果我将语音识别放到另一个线程中,这不会让计算机视觉部分显示内容。这就是为什么我尝试在其他线程中跳跃其他内容
    • 听起来你问错了问题。我想您正在寻找 SpeechRecognitionEngine.RecognizeAsync() 方法。 AI 软件通常对线程不是很聪明,请务必使用 Thread::SetApartmentState() 来选择 STA。
    • 关于死锁,你的意思是应用程序关闭后也能发生锁吗?或者我猜新的通知窗口已经关闭了?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-26
    • 1970-01-01
    • 2022-11-19
    • 2020-07-13
    相关资源
    最近更新 更多