2014-12-07 66 views
6

我有一个BackgroundWorker一个WinForm:的BackgroundWorker:出现InvalidOperationException在RunWorkerCompleted

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Drawing; 
using System.Linq; 
using System.Text; 
using System.Windows.Forms; 
using SeoTools.Utils; 

namespace SeoTools.UI 
{ 
    public partial class UIProgress : Form 
    { 
     public UIProgress(DoWorkEventHandler doWorkEventHandler, RunWorkerCompletedEventHandler runWorkerCompletedEventHandler) 
     { 
      InitializeComponent(); 
      this.backgroundWorker.WorkerReportsProgress = true; 
      this.backgroundWorker.WorkerSupportsCancellation = true; 
      this.backgroundWorker.DoWork += doWorkEventHandler; 
      this.backgroundWorker.RunWorkerCompleted += runWorkerCompletedEventHandler; 
     } 

     public void Start() 
     { 
      var foo = SynchronizationContext.Current; 
      backgroundWorker.RunWorkerAsync(); 
     } 

     private void btnStop_Click(object sender, EventArgs e) 
     { 
      btnStop.Enabled = false; 
      btnStop.Text = "Stopping..."; 
      backgroundWorker.CancelAsync(); 
     } 

     private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) 
    { 
     try 
     { 
      wdgProgressBar.Value = e.ProgressPercentage; 
      if (this.Visible == false) 
      { 
       this.ShowDialog(); 
       this.Update(); 
      } 
     } 
     catch (InvalidOperationException) {} 
    } 

     private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
     { 
      this.Hide(); //Here I get a InvalidOperationException 
      this.Dispose(); 
     }  
    } 
} 

我第一次运行这个它工作正常。但拨打this.Hide()时第二次获得InvalidOperationException

“附加信息:跨线程操作无效:控制'UIProgress'从其创建的线程以外的线程访问。”

奇怪的是在开始第一次运行foo()从一个WindowsFormsSyncronizationContext,但在第二次尝试这是一个System.Threading.SyncronizationContext

我正在写的应用程序是一个ExcelDna插件。

编辑

start()被称为是这样的:

UIProgress uiProgress = new UIProgress(
       delegate(object sender, DoWorkEventArgs args) 
       { 
        .... 
       }, 
       delegate(object sender, RunWorkerCompletedEventArgs args) 
        { 
         ... 
        } 
      ); 
      uiProgress.Start(); 
+0

如何调用'Start'? – kennyzx 2014-12-07 15:23:00

+0

@kennyzx我已更新我的问题 – 2014-12-07 17:21:24

+1

我在SyncronizationContext上发现了一个旧的[post](http://blogs.msdn.com/b/mattdotson/archive/2006/02/13/531315.aspx),该技术,你可以保存'WindowsFormsSyncronizationContext'供以后使用。不知道如何切换到另一个'SyncronizationContext',也许是Excel-DNA环境...需要一些艰苦的调试时间。 – kennyzx 2014-12-08 13:41:25

回答

7

你的start()方法必须从UI线程上运行,允许BackgroundWorker的正常运行代码调用。这不是当你得到这个例外。保护代码添加到您的方法,这样你就可以诊断这个硬伤:

public void Start() 
    { 
     if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA) { 
      throw new InvalidOperationException("Bug! Code called from a worker thread"); 
     } 
     backgroundWorker.RunWorkerAsync(); 
    } 

现在,您可以设置throw语句断点,并使用调试器的调用堆栈窗口,找出为什么发生这种情况。

+0

InvokeRequired对我来说总是假的。 – 2014-12-07 17:17:25

+0

这意味着UIProgress对象是在错误的线程上创建的。代码已更新。它在发布时更具普遍性,但是当您将测试放入构造函数时,您会得到更好的堆栈跟踪。 – 2014-12-12 11:33:22

1

使用表单上的BeginInvoke()方法:

// http://msdn.microsoft.com/en-us/library/0b1bf3y3(v=vs.110).aspx

private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
     this.BeginInvoke(new InvokeDelegate(InvokeMethod));    
    } 

    public delegate void InvokeDelegate(); 

    public void InvokeMethod() 
    { 
     this.Hide(); 
     this.Dispose(); 
    } 
3

您正在呼吁后台线程UI操作。这是该例外的原因。我会使用完全不同的方法使进度形式最好的是使用Task with IProgress。另一种方法是使用此: `私人无效backgroundWorker_ProgressChanged(对象发件人,ProgressChangedEventArgs E) {

this.UpdateOnMainThread(
    () => 
    { 
     wdgProgressBar.Value = e.ProgressPercentage; 
     if (this.Visible == false) 
     { 
     this.ShowDialog(); 
     this.Update(); 
     } 
    }); 
} 

private void UpdateOnMainThread(Action action) 
{ 
    if (this.InvokeRequired) 
    { 
    this.BeginInvoke((MethodInvoker) action.Invoke); 
    } 
    else 
    { 
    action.Invoke(); 
    } 
} 

private void backgroundWorker_RunWorkerCompleted(object sender , RunWorkerCompletedEventArgs e) 
{ 
    this.UpdateOnMainThread(
    () => 
    { 
     this.Hide(); //Here I get a InvalidOperationException 
     this.Dispose(); 
    }); 

}` 
1

我想你可以在这里找到一些帮助:BackgroundWorker hide form window upon completion。 但是,不要忘记分离BackgroundWorker事件并停止BackgroundWorker它自己像这里解释:Proper way to Dispose of a BackGroundWorker。 这个问题可以在

this.Dispose(); 

backgroundWorker_RunWorkerCompleted事件。因为你正在处理表单页面。那是你想要做的吗?或者你想处置BackgroundWorker?处理表单页面所有资源都被释放,所以第二次做this.Hide();可能是一个错误。

欲了解更多信息,你可以看到这个链接:C# Form.Close vs Form.DisposeForm.Dispose Method

1

你'从无法操作UI的线程调用主线程。 最简单的方法是使用匿名委托调用。

更改此:

 if (this.Visible == false) 
     { 
      this.ShowDialog(); 
      this.Update(); 
     } 

对于这一点:

this.Invoke((MethodInvoker) delegate { 
     if (this.Visible == false) 
     { 
      this.ShowDialog(); 
      this.Update(); 
     }  
}); 

这不是最优化的方式,但赫然快速做这项工作没有太多的重新编码。 :)

相关问题