2012-11-27 60 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事件的结束。 –