C#如何从非UI线程调用backgroundWorker?

4
我正在尝试像下面的代码一样加载loadingForm。但它不起作用,loadingForm不会消失,事件RunWorkerCompleted也不会被调用。
另外,我需要多次调用loadingFormbackgroundWorker,所以每次调用后如何完全处理loadingFormbackgroundWorker
我认为我的代码有很多问题,但我不知道如何解决它。你能否向我展示如何解决我的问题并指出我需要修复的地方?非常感谢。
public partial class loginForm : Form
{
     //....
     private loadingForm lf;
     private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
     {
          lf.Show();
          While (backgroundWorker1.isBusy)
               Application.DoEvents();
     }
     private void backgroundWorker1_RunWorkerCompleted(object sender, DoWorkEventArgs e)
     {
          lf.Close();
     }
     private void connect()
     {
          //....
          Thread mainThread = new Thread(ThreadStart(listentoServer));
          mainThread.Start();
     }
     private void listentoServer()
     {
          //....
          lf = new loadingForm();
          backgroundWorker1.RunWorkerAsync();
          //....
          backgroundWorker1.CancelAsync();
          //.... 
     }
}
3个回答

3
你的代码有很多问题。如果可以的话,请退后一步并描述你想要做什么。
BackgroundWorker使用基于事件的异步模式(EAP)。因此,它需要一个线程上下文来运行。UI线程满足此要求,但手动创建的Thread实例不满足此要求(除非您安装一个或将实例设置为辅助UI线程)。
同样,UI组件绑定到特定线程。它们需要一个STA线程来执行消息泵(例如Application.DoEvents)。
在我看来,你正在从该线程创建手动Thread,然后创建UI组件(所以你知道该线程应该是STA并包含一个消息泵循环,但这些都不在你的代码中)。然后该线程启动了一个BGW,该BGW执行消息泵。
不清楚你想要达到什么目的-也许是在单独的线程中显示对话框?
WinForms应用程序中的多个UI线程不是官方支持的场景,尽管有些人已经使其工作。我从来没有看到过需要这样做的情况。

谢谢你的回答。我想做的是在UI线程之外的另一个线程中显示一个带有跑马灯进度条的小表单(如上面的代码所述,这个小表单是在主线程中显示的)。因为如果我不使用另一个线程来显示这个表单,跑马进度条就不会被加载。这就是我的问题所在。你知道一种加载带有进度条的小表单的方法吗? - PeteMerry
我认为你正在错误的方式来解决这个问题。而不是通过执行某些操作和创建第二个UI线程来显示小进度条来阻止您的主要UI线程,请将操作分解到单独的线程中(例如,使用BackgroundWorker)。从您的主要UI线程中,然后可以启动后台操作,显示小的进度条(模态),并有BackgroundWorker.RunWorkerCompleted事件关闭小的进度条。 - Stephen Cleary
但是在关闭小进度条后,操作仍然需要继续运行。backgroundWorker.RunWorkerCompleted事件会停止运行其他在backgroundWorker上运行的操作吗?而且,加载小进度表单的事件在操作中被调用(就像上面的代码一样,在listentoServer内部调用,如果我使用backgroundWorker而不是mainThread,则将在另一个线程上)。我可以在操作中使用UI线程来加载小进度表单吗? - PeteMerry
每个 BackgroundWorker 都是独立的。如果您需要小进度窗体保持打开状态,则不要在 RunWorkerCompleted 中关闭它。一般的指导方针如下:1)将所有 UI 访问和表单放在单个线程中;2)使用 BackgroundWorker(或 Task 等)进行独立的后台任务。 - Stephen Cleary

1
根据您所展示的内容(虽然不完整,因此可能不是问题),您没有将事件连接到backgroundWorker_DoWorkbackgroundWorker_RunWorkerCompleted事件处理程序。在某个地方(在实例化backgroundWorker之后),您应该有这样的代码:
backgroundWorker.DoWork += new EventHandler(backgroundWorker_DoWork);
backgroundWorker.RunWorkerCompleted += new EventHandler(backgroundWorker_RunWorkerCompleted);

免责声明,此文是手写的,因此事件名称或EventHandler类型可能不正确。


谢谢你的回答,这两行代码已经在窗体设计器生成的代码中了。这就是为什么我可以调用DoWork,但我不知道如何调用RunWorkCompleted。 - PeteMerry

1

我真的不知道如何彻底修复你的代码,或者你的代码是否按照你的方式工作,我只能给你以下指导。

  1. 使用后台工作者的 CancellationPending 属性,而不是 IsBusy 属性
  2. 在使用 Windows 窗体和线程化代码时,始终使用 Invoke/BeginInvoke 方法,以确保将回调调度到控件所在的线程。

我确实在backgroundWorker1_DoWork和backgroundWorker1_RunWorkerCompleted中使用了BeginInvoke,但问题仍然存在,所以在使用beginInvoke之前我发布了代码。如果我上面的代码不能按照我想要的方式工作,您有什么建议吗?每当我的应用程序正在下载或上传时,我想显示一个带有进度条的小表单。这就是为什么我需要使用backgroundWorker来加载loadingForm的原因。我已经困在这一点上几天了。非常感谢您的帮助。提前致谢。 - PeteMerry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接