2012-11-27 69 views
1

美好的一天!在Dispose中等待Backgroundworker()

我有自定義popupcontainer control - comboBox在那裏你可以把其他控件,他們會出現彈出。在我的情況下,它包含datagrid。我想讓它工作得更快 - 只有當用戶決定彈出我的控件時,我才需要該數據網格。我決定將datagrid創建移動到其他線程,並使用Backgroundworker,因爲它非常適合用戶界面,我不想混亂Control.Invoke。這裏是我的簡化代碼:

protected override void OnCreateControl() 
    { 
     base.OnCreateControl(); 
     CreateContainer(); 
    } 
    protected virtual void CreateContainer() 
    { 
     _worker = new BackgroundWorker(); 
     _worker.DoWork += DoActionsOnAsyncWork; 
     _worker.RunWorkerCompleted += AsyncWorker_RunWorkerCompleted; 
     _worker.RunWorkerAsync(this); 
    } 
    protected void DoActionsOnAsyncWork(object sender, DoWorkEventArgs e) 
    { 
     var _grid = ///Create and initialize local grid here 
     e.Result = _grid; // Throw it to RunWorkerCompleted event 
    } 
    private void AsyncWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
     if (!e.Cancelled && e.Error == null && !IsDisposed && !IsDisposing) 
     { 
      if (!_closing) 
      { 
       this.Grid = e.Result as MyGrid; 
       AttachEventHandlersToGrid(); 
      } else 
       (e.Result as MyGrid).Dispose(); // Get rid of grid 
     } 

     // Get rid of worker 
     _worker.DoWork -= DoActionsOnAsyncWork; 
     _worker.RunWorkerCompleted -= AsyncWorker_RunWorkerCompleted; 
     _worker.Dispose(); 
     _worker = null; 
    } 
    protected override void Dispose(bool disposing) 
    { 
     if (disposing) 
     { 
      WaitForBackgroundWorker(); 
      // Dispose other stuff 
      ... 
     } 
     base.Dispose(disposing); 
    } 

    protected void WaitForBackgroundWorker() 
    { 
     if (_worker == null || 
      !_worker.IsBusy) 
      return; 
     _closing = true; 

     while (_worker != null && _worker.IsBusy) 
      Application.DoEvents(); // Hack throw worker.RunWorkerCompleted on top of msg queue 
    } 

現在,我需要等待_worker爲了妥善處理我的控制(形式爲更大範圍內)。此外_worker創建grid也是需要處理的。我的問題是 - 如何在沒有Application.DoEvents()的情況下等待backgroundworker。因爲有時(通常在遠程桌面 - 也許是其他一些繪畫算法?),它會導致整個應用程序掛起。調用堆棧:

mscorlib.dll!System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle waitableSafeHandle, long millisecondsTimeout, bool hasThreadAffinity, bool exitContext) + 0x1f bytes  
mscorlib.dll!System.Threading.WaitHandle.WaitOne(long timeout, bool exitContext) + 0x23 bytes 
mscorlib.dll!System.Threading.WaitHandle.WaitOne(int millisecondsTimeout, bool exitContext) + 0x1c bytes  
System.Windows.Forms.dll!System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle waitHandle) + 0x96 bytes  
System.Windows.Forms.dll!System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control caller, System.Delegate method, object[] args, bool synchronous) + 0x34b bytes 
System.Windows.Forms.dll!System.Windows.Forms.Control.Invoke(System.Delegate method, object[] args) + 0x50 bytes  
System.Windows.Forms.dll!System.Windows.Forms.WindowsFormsSynchronizationContext.Send(System.Threading.SendOrPostCallback d, object state) + 0x56 bytes  
System.dll!Microsoft.Win32.SystemEvents.SystemEventInvokeInfo.Invoke(bool checkFinalization, object[] args) + 0x66 bytes  
System.dll!Microsoft.Win32.SystemEvents.RaiseEvent(bool checkFinalization, object key, object[] args) + 0x110 bytes  
System.dll!Microsoft.Win32.SystemEvents.RaiseEvent(object key, object[] args) + 0xe bytes 
System.dll!Microsoft.Win32.SystemEvents.OnUserPreferenceChanged(int msg, System.IntPtr wParam, System.IntPtr lParam) + 0x76 bytes 
System.dll!Microsoft.Win32.SystemEvents.WindowProc(System.IntPtr hWnd, int msg, System.IntPtr wParam, System.IntPtr lParam) + 0x2c6 bytes 
[Native to Managed Transition] 
[Managed to Native Transition] 
System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(System.IntPtr dwComponentID, int reason, int pvLoopData) + 0x357 bytes  
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int reason, System.Windows.Forms.ApplicationContext context) + 0x33d bytes 
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int reason, System.Windows.Forms.ApplicationContext context) + 0x5f bytes  
System.Windows.Forms.dll!System.Windows.Forms.Application.DoEvents() + 0x18 bytes 
MyControl.WaitForBackgroundWorker() Line 874 + 0x1b bytes   
... // Many recursive disposes  
Form.Dispose() 
+1

你不能等待,這將導致死鎖之間。檢查這個答案:http://stackoverflow.com/a/1732361/17034 –

+0

@Hans Passant然後,我感到困惑。是否真的沒有辦法等待'backgroundworker'完成它的兩個事件 - DoWork和RunWorkerCompleted?我有'QueryPopUp'事件,我使用Application.DoEvents()沒有循環,它的工作(幸運?)。我在'backgroundworker.DoWork'事件中通過Thread.Sleep()測試了它。還有一個詞可能會調用Application.DoEvents()始終強制backgroundworker.RunWorkerCompleted完成? – nikita

+0

RunWorkerCompleted是這個問題,它只能在UI線程正在泵送消息時運行。是的,DoEvents是避免僵局的一種手段。龍住在那裏。 –

回答

1

因爲該行的:

while (_worker != null && _worker.IsBusy) 
      Application.DoEvents(); // Hack throw worker.RunWorkerCompleted on top of msg queue 

即使DoWork的完成,while循環將阻止RunWorkerCompleted得到執行,因爲他們是在同一個線程(while循環一直執行。

改變這樣的代碼:

private void AsyncWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
     { 
      if (!e.Cancelled && e.Error == null && !IsDisposed && !IsDisposing) 
      { 
       if (!_closing) 
       { 
        this.Grid = e.Result as MyGrid; 
        AttachEventHandlersToGrid(); 
       } 
       else 
       { 
        (e.Result as MyGrid).Dispose(); // Get rid of grid 
       } 
      } 

      // Get rid of worker 
      _worker.DoWork -= DoActionsOnAsyncWork; 
      _worker.RunWorkerCompleted -= AsyncWorker_RunWorkerCompleted; 
      _worker.Dispose(); 
      _worker = null; 
      if(_closing) 
       this.Dispose(); //call Dispose again, now you know that worker has finished 
     } 
     protected bool HasBackgroundWorkerFinished() 
     { 
      if (_worker == null || 
       !_worker.IsBusy) 
       return true; 
      _worker.CancelAsync(); 
      _closing = true; //this means that worker is busy 
      return false; 
     } 
     protected override void Dispose(bool disposing) 
     { 
      bool dispose = true; 
      if (disposing) 
      { 
       dispose = HasBackgroundWorkerFinished(); 
       if (dispose) 
       { 
        // Dispose other stuff 
       } 
      } 
      if(dispose) //don't dispose if worker didn't finish 
       base.Dispose(disposing); 
     } 

設置_worker接受取消和多次

if (_worker.CancellationPending) 
{ 
     e.Cancel = true; 
     return; 
} 

將此代碼添加到您的DoWork代碼的所有主要部分代碼

+0

這是一個很好的解決方案,因爲我不會弄亂表單關閉事件,但它會阻止GC在form.Close()事件上收集control => form。不會GC以某種方式標記表單對象,因爲它未能處理它?它會在下一個GC.Collect中處理嗎? – nikita

+0

處理將在工人完成後再次調用。檢查DoWork事件的結束。 –

相關問題