【问题标题】:Waiting for Backgroundworker in Dispose()在 Dispose() 中等待 Backgroundworker
【发布时间】:2012-11-27 20:39:54
【问题描述】:

美好的一天!

我有自定义popupcontainer control - 组合框,您可以在其中放置其他控件,它们会出现在弹出窗口中。就我而言,它包含datagrid。我想让它快速工作——只有当用户决定弹出我的控件时,我才需要那个数据网格。我决定将数据网格创建移动到其他线程并使用Backgroundworker,因为它非常适合 UI,我不想弄乱 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()

【问题讨论】:

  • 你不能等待,这会导致死锁。检查这个答案:stackoverflow.com/a/1732361/17034
  • @Hans Passant 然后我很困惑。难道真的没有办法等待backgroundworker 完成它的两个事件——DoWorkRunWorkerCompleted?我有 QueryPopUp 事件,我使用 Application.DoEvents() 没有循环并且它有效(幸运?)。我已经在backgroundworker.DoWork 事件中通过 Thread.Sleep() 对其进行了测试。换句话来说,可能调用 Application.DoEvents() 总是强制 backgroundworker.RunWorkerCompleted 完成?
  • RunWorkerCompleted 是问题所在,它只能在 UI 线程正在泵送消息时运行。是的,DoEvents 是一种避免死锁的技巧。龙住在那里。

标签: c# .net winforms multithreading backgroundworker


【解决方案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 代码

【讨论】:

  • 这是一个很好的解决方案,因为我不会弄乱表单关闭事件,但它会阻止 GC 在 form.Close() 事件上收集控制 => 表单。 GC不会以某种方式标记表单对象,因为它未能处理它吗?它会在下一次 GC.Collect 上处理吗?
  • Dispose 将在工作人员完成后再次调用。检查 DoWork 事件的结束。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多