从BackgroundWorker使用WebBrowser创建对话框
本文关键字:创建 对话框 WebBrowser 使用 BackgroundWorker | 更新日期: 2023-09-27 18:20:01
我有一个网页抓取程序。从主表单中,您可以选择一个网站和一个客户端,然后单击Go,它将启动一个BackgroundWorker线程,该线程利用WebRequest和HTMLAgilityPack来处理一系列针对SiteX和ClientX的请求,每个请求都是多个页面。每个BackgroundWorker线程都有运行的验证,如果遇到问题,它会弹出一个对话框,让用户中止线程、中止请求、忽略错误,或者(如果从IDE运行)进入代码。我希望此对话框包含一个WebBrowser控件,以显示渲染良好的有问题的HTML页面。然而,因为它是从BackgroundWorker线程调用的,所以我得到了异常"当前线程不在单线程单元中"。
以下是创建对话框的功能:
protected bool ValidatePage( bool pagePasses, string msg ) {
if ( pagePasses == false ) {
AbortIgnoreSuspend ais = new AbortIgnoreSuspend( responsehtml, msg );
ais.ShowDialog();
switch ( ais.DialogResult ) {
case DialogResult.Abort: // Aborts entire thread
Abort = true;
worker.CancelAsync();
return false;
case DialogResult.Cancel: // Aborts this case
Abort = true;
return false;
case DialogResult.Ignore: // Ignore and continue
return true;
case DialogResult.Retry: // Debug
Debug.Assert( false, "Suspending Thread" );
return true; // Will return you to calling thread and allow you to continue
default:
return true;
}
}
return true;
}
我发现了一些例子,在ApartmentState设置为ApartmentState.STA的情况下启动Thread()可以创建我的WebBrowser,所以我进行了以下代码调整:
protected bool ValidatePage( bool pagePasses, string msg ) {
if ( pagePasses == false ) {
bool setAbort = false;
bool assertError = false;
bool cancelWorker = false;
bool returnContinue = true;
Thread th = new Thread( () => {
AbortIgnoreSuspend ais = new AbortIgnoreSuspend( responsehtml, msg );
ais.ShowDialog();
switch ( ais.DialogResult ) {
case DialogResult.Abort: // Aborts entire thread
setAbort = true;
cancelWorker = true;
returnContinue = false;
break;
case DialogResult.Cancel: // Aborts this case
setAbort = true;
returnContinue = false;
break;
case DialogResult.Ignore: // Ignore and continue
returnContinue = true;
break;
case DialogResult.Retry: // Debug
assertError = true;
returnContinue = true; // Will return you to calling thread and allow you to continue
break;
default:
returnContinue = true;
break;
}
} );
th.SetApartmentState( ApartmentState.STA );
th.Start();
th.Join();
Abort = setAbort;
Debug.Assert( !assertError, "Suspending thread for debugging" );
if ( cancelWorker ) { worker.CancelAsync(); }
return returnContinue;
}
return true;
}
这似乎是可行的(我用一个BackgroundWorker测试过),但我很确定,由于我缺乏使用线程的经验,我在线程安全性方面犯了一些错误。我做错了什么,错过了什么?
根据在.NET中创建线程的方式,.NET会将该线程的单元设置为名为MTA或STA的野兽。MTA和STA都是COM技术;除此之外,它们在.NET中几乎没有用处(正如MS的.NET文档中所提到的)。MS喜欢将某些.NET线程默认为MTA,如果您没有明确设置,我怀疑您的线程不是主要的UI STA线程。
我看到你在.NET中使用了相当漂亮的BackgroundWorker。如果你将WorkerReportsProgress设置为true,则为ProgressChanged设置一个处理程序,并将你的对话框代码移到这个新处理程序中调用,应该可以解决你的问题。
ProgressChanged实际上并不需要报告进度,您可以将其用于任何事情,特别是当您需要切换线程上下文时。传递给ReportProgress的参数也可以是您喜欢的任何对象。
为什么这样做?ProgressChanged自动执行从工作线程上下文到主用户界面(UI)线程上下文的线程编组。只有在UI线程中才能安全地执行UI调用。