2012-07-10 107 views
1

假设我有一个GUI应用程序,它具有一个在应用程序的生命周期中运行的后台线程。当我关闭应用程序时,我想干净地关闭这些后台线程中的任何一个。事实上,我经常有几个线程可以运行数据收集和处理活动,而无需挂起GUI。如何在不调用application :: doevents的情况下关闭后台工作线程?

下面的例子演示了这个问题;即如果您取消后台工作,它会尝试在主线程上调用worker complete方法。如果没有致电Application::DoEvents()的代码将无限期地挂起,但我之前遇到过调用DoEvents的问题,而我的直觉告诉我这是不好的做法。

所以问题是,当我的应用程序退出时,干净关闭后台工作线程的正确方法是什么?

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

/// <summary> 
/// Summary for Form1 
/// 
/// WARNING: If you change the name of this class, you will need to change the 
///   'Resource File Name' property for the managed resource compiler tool 
///   associated with all .resx files this class depends on. Otherwise, 
///   the designers will not be able to interact properly with localized 
///   resources associated with this form. 
/// </summary> 
public ref class Form1 : public System::Windows::Forms::Form 
{ 
private: System::ComponentModel::BackgroundWorker^ backgroundWorker1; 

public: 
    Form1(void) 
    { 
     InitializeComponent(); 

     // 
     // backgroundWorker1 
     // 
     this->backgroundWorker1 = (gcnew System::ComponentModel::BackgroundWorker()); 
     this->backgroundWorker1->DoWork += gcnew System::ComponentModel::DoWorkEventHandler(this, &Form1::backgroundWorker1_DoWork); 
     this->backgroundWorker1->RunWorkerCompleted += gcnew System::ComponentModel::RunWorkerCompletedEventHandler(this, &Form1::backgroundWorker1_RunWorkerCompleted); 
     this->backgroundWorker1->ProgressChanged += gcnew System::ComponentModel::ProgressChangedEventHandler(this, &Form1::backgroundWorker1_ProgressChanged); 
     this->backgroundWorker1->WorkerReportsProgress = true; 
     this->backgroundWorker1->WorkerSupportsCancellation = true; 

     // 
     //TODO: Add the constructor code here 
     // 
     backgroundWorker1->RunWorkerAsync(); 
    } 

protected: 
    /// <summary> 
    /// Clean up any resources being used. 
    /// </summary> 
    ~Form1() 
    { 
     if(backgroundWorker1 != nullptr) 
     { 
      backgroundWorker1->CancelAsync(); 
     } 

     while(backgroundWorker1->IsBusy == true) 
     { 
      System::Diagnostics::Debug::WriteLine("Waiting for background worker to exit.."); 
      System::Threading::Thread::Sleep(1000); 
      // Application::DoEvents(); <-- Don't want to do this but what are the alternatives? 
     } 

     System::Diagnostics::Debug::WriteLine("Form1 destructor complete!"); 
    } 

private: System::Void backgroundWorker1_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) { 
      while(backgroundWorker1->CancellationPending == false) 
      { 
       System::Diagnostics::Debug::WriteLine("Working.."); 
       System::Threading::Thread::Sleep(1000); 
      } 
     } 

private: System::Void backgroundWorker1_ProgressChanged(System::Object^ sender, System::ComponentModel::ProgressChangedEventArgs^ e) { 
     } 

private: System::Void backgroundWorker1_RunWorkerCompleted(System::Object^ sender, System::ComponentModel::RunWorkerCompletedEventArgs^ e) 
     { 
      System::Diagnostics::Debug::WriteLine("Exiting.."); 
      System::Threading::Thread::Sleep(1000); 

     } 

private: 
    /// <summary> 
    /// Required designer variable. 
    /// </summary> 
    System::ComponentModel::Container ^components; 

#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->SuspendLayout(); 
     // 
     // Form1 
     // 
     this->AutoScaleDimensions = System::Drawing::SizeF(6, 13); 
     this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font; 
     this->ClientSize = System::Drawing::Size(443, 343); 
     this->Name = L"Form1"; 
     this->Text = L"Form1"; 
     this->ResumeLayout(false); 

    } 
#pragma endregion 

}; 
+0

找到了一个可能的解决方案:http://social.msdn.microsoft.com/Forums/nl/csharpgeneral/thread/14679d00-3d46-4de4-be87-e66c196f0638 – 2012-07-10 14:41:21

+0

而且[一个可能的重复](http:// stackoverflow .com/questions/4732737/how-to-stop-backgroundworker-correct)至少[主张使用DoEvents的一个答案](http://stackoverflow.com/a/7625188/15369)... – 2012-07-10 14:49:50

+0

[一些有趣的背景信息](http://blogs.msdn.com/b/jfoscoding/archive/2005/08/06/448560.aspx)DoEvents ... – 2012-07-10 14:52:19

回答

1

当BackgroundWorker的触发该RunWorkerCompleted事件,它这样做的主要GUI线程,这是析构函数上运行的一个上。当您调用DoEvents时,它使框架有机会运行等待在该线程上运行的其他任务,包括RunWorkerCompleted事件。

我添加Thread::CurrentThread->ManagedThreadId到您的调试消息(和取消注释Application::DoEvents()),这里是我得到的:

Starting background worker from thread 1 
Working on thread 3.. 
Working on thread 3.. 
Waiting on thread 1 for background worker to exit.. 
Exiting on thread 1.. 
Form1 destructor complete! 

所以,如果你不想使用应用程序::的DoEvents(),我使用不同的机制来告诉UI线程该工作者已经退出。一个ManualResetEvent将在这里很好地完成这个技巧。

private: 
    ManualResetEvent^ mre; 

public: 
    Form1(void) 
    { 
     InitializeComponent(); 

     this->backgroundWorker1 = (gcnew System::ComponentModel::BackgroundWorker()); 
     this->backgroundWorker1->DoWork += gcnew System::ComponentModel::DoWorkEventHandler(this, &Form1::backgroundWorker1_DoWork); 
     // Don't use this->backgroundWorker1->RunWorkerCompleted 
     this->backgroundWorker1->ProgressChanged += gcnew System::ComponentModel::ProgressChangedEventHandler(this, &Form1::backgroundWorker1_ProgressChanged); 
     this->backgroundWorker1->WorkerReportsProgress = true; 
     this->backgroundWorker1->WorkerSupportsCancellation = true; 

     this->mre = gcnew ManualResetEvent(false); 

     System::Diagnostics::Debug::WriteLine("Starting background worker from thread {0}", Thread::CurrentThread->ManagedThreadId); 
     backgroundWorker1->RunWorkerAsync(); 
    } 

protected: 
    ~Form1() 
    { 
     if(backgroundWorker1 != nullptr) 
     { 
      backgroundWorker1->CancelAsync(); 
     } 

     System::Diagnostics::Debug::WriteLine("Waiting on thread {0} for background worker to exit..", Thread::CurrentThread->ManagedThreadId); 
     this->mre->WaitOne(); 

     System::Diagnostics::Debug::WriteLine("Form1 destructor complete!"); 
    } 

private: 
    System::Void backgroundWorker1_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) 
     { 
      try 
      { 
       while(backgroundWorker1->CancellationPending == false) 
       { 
        System::Diagnostics::Debug::WriteLine("Working on thread {0}..", Thread::CurrentThread->ManagedThreadId); 
        System::Threading::Thread::Sleep(1000); 
       } 
      } 
      finally 
      { 
       this->mre->Set(); 
      } 
     } 

现在,所有的说法,你可能要考虑在这里使用一个普通的线程,而不是一个BackgroundWorker。通过将“完成”和“进度”事件触发到UI线程上,我认为它更适合于直接在UI线程上执行的任务太长,但几秒钟之后结果将显示在UI中。 (在UI线程上触发事件可以很容易地用操作的结果来更新UI控件。)对于你在这里展示的内容,表单打开的整个过程中运行的东西,你没有变得很大受益于BackgroundWorker,所以您可以创建一个线程并自行完成。

相关问题