BackgroundWorker 如何决定 运行 RunWorkerCompleted 处理程序的线程?
How does BackgroundWorker decide on which thread to run the RunWorkerCompleted handler?
我想弄清楚 BGW 如何决定在 RunWorkerCompleted 处理程序完成后 运行 哪个线程。
我的初始测试使用 WinForm 应用程序:
在 UI 线程上,我开始 bgw1.RunWorkerAsync()
。然后我尝试在 2 个不同的地方从 bgw2.RunWorkerAsync()
到 bgw1
开始:
bgw1_DoWork()
方法
- 或
bgw1_RunWorkerCompleted()
方法。
我最初的猜测是 BGW 应该记住它是在哪个线程上启动的,并且 return 该线程在其工作完成后执行 RunWorkerCompleted
事件处理程序。
但是测试结果很奇怪:
测试 1
如果我在 bgw1_RunWorkerCompleted()
中启动 bgw2.RunWorkerAsync()
,bgw2_RunWorkerCompleted()
总是在 UI 线程上执行。
UI @ thread: 9252
bgw1_DoWork @ thread: 7216
bgw1_RunWorkerCompleted @ thread: 9252 <------ ALWAYS same as UI thread 9252
bgw2_DoWork @ thread: 7216
bgw2_RunWorkerCompleted @ thread: 9252
bgw1_DoWork @ thread: 7216
bgw1_RunWorkerCompleted @ thread: 9252
bgw2_DoWork @ thread: 1976
bgw2_RunWorkerCompleted @ thread: 9252
bgw1_DoWork @ thread: 7216
bgw1_RunWorkerCompleted @ thread: 9252
bgw2_DoWork @ thread: 1976
bgw2_RunWorkerCompleted @ thread: 9252
bgw1_DoWork @ thread: 7216
bgw1_RunWorkerCompleted @ thread: 9252
bgw2_DoWork @ thread: 1976
bgw2_RunWorkerCompleted @ thread: 9252
bgw1_DoWork @ thread: 7216
bgw1_RunWorkerCompleted @ thread: 9252
bgw2_DoWork @ thread: 7216
bgw2_RunWorkerCompleted @ thread: 9252
测试 2
但是如果我在 bgw1_DoWork()
中启动 bgw2.RunWorkerAsync()
,我认为 bgw2
应该记住 bgw1.DoWork()
线程并且 bgw2_RunWorkerCompleted()
应该总是 return 使用 bgw1_DoWork()
线程。但实际上不是。
UI @ thread: 6352
bgw1_DoWork @ thread: 2472
bgw1_RunWorkerCompleted @ thread: 6352
bgw2_DoWork @ thread: 18308
bgw2_RunWorkerCompleted @ thread: 2472
bgw1_DoWork @ thread: 12060 <------- bgw1_DoWork
bgw1_RunWorkerCompleted @ thread: 6352
bgw2_DoWork @ thread: 8740
bgw2_RunWorkerCompleted @ thread: 12060 <------- SOME SAME AS bgw1_DoWork
bgw1_DoWork @ thread: 7028
bgw1_RunWorkerCompleted @ thread: 6352
bgw2_DoWork @ thread: 2640
bgw2_RunWorkerCompleted @ thread: 7028
bgw1_DoWork @ thread: 5572 <------- HERE is 5572
bgw1_RunWorkerCompleted @ thread: 6352
bgw2_DoWork @ thread: 32
bgw2_RunWorkerCompleted @ thread: 2640 <------- HERE is not 5572
bgw1_DoWork @ thread: 10924
bgw1_RunWorkerCompleted @ thread: 6352
bgw2_DoWork @ thread: 12932
bgw2_RunWorkerCompleted @ thread: 10924
那么,BGW是如何决定哪个线程运行完成事件的呢?
测试代码:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private BackgroundWorker bgw1;
private BackgroundWorker bgw2;
private void Form1_Load(object sender, EventArgs e)
{
this.textBox1.Text += "UI @ thread: " + GetCurrentWin32ThreadId() + Environment.NewLine;
bgw1 = new BackgroundWorker();
bgw1.DoWork += bgw1_DoWork;
bgw1.RunWorkerCompleted += bgw1_RunWorkerCompleted;
bgw2 = new BackgroundWorker();
bgw2.DoWork += bgw2_DoWork;
bgw2.RunWorkerCompleted += bgw2_RunWorkerCompleted;
}
void bgw1_DoWork(object sender, DoWorkEventArgs e)
{
Int32 tid = GetCurrentWin32ThreadId();
this.textBox1.Invoke(new MethodInvoker(() => { this.textBox1.Text += "bgw1_DoWork @ thread: " + tid + Environment.NewLine; })); //"invoked" on UI thread.
Thread.Sleep(1000);
//this.bgw2.RunWorkerAsync(); // <==== START bgw2 HERE
}
void bgw2_DoWork(object sender, DoWorkEventArgs e)
{
Int32 tid = GetCurrentWin32ThreadId();
this.textBox1.Invoke(new MethodInvoker(() => { this.textBox1.Text += "bgw2_DoWork @ thread: " + tid + Environment.NewLine; })); //"invoked" on UI thread.
Thread.Sleep(1000);
}
void bgw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//this will go back to the UI thread, too.
this.textBox1.Text += "bgw1_RunWorkerCompleted @ thread: " + GetCurrentWin32ThreadId() + Environment.NewLine;
this.bgw2.RunWorkerAsync(); // <==== OR START bgw2 HERE
}
void bgw2_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.textBox1.Text += "bgw2_RunWorkerCompleted @ thread: " + GetCurrentWin32ThreadId() + Environment.NewLine;
}
private void button1_Click(object sender, EventArgs e)
{
this.bgw1.RunWorkerAsync();
}
[DllImport("Kernel32", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)]
public static extern Int32 GetCurrentWin32ThreadId();
}
测试 3
然后我尝试使用控制台应用程序。虽然我仍然像测试 1 一样在 bgw1_RunWorkerCompleted()
中启动 bgw2.RunWorkerAsync()
,但 bgw1
或 bgw2
都没有在主线程上完成。这与测试 1 非常不同。
我原以为这里的主线程是 UI 线程的 counterpart。 但似乎 UI 线程与控制台主线程的处理方式不同。
-------------
Main @ thread: 11064
bgw1_DoWork @ thread: 15288
bgw1_RunWorkerCompleted @ thread: 17260
bgw2_DoWork @ thread: 17260
bgw2_RunWorkerCompleted @ thread: 15288
-------------
Main @ thread: 11064
bgw1_DoWork @ thread: 12584
bgw1_RunWorkerCompleted @ thread: 17260
bgw2_DoWork @ thread: 17260
bgw2_RunWorkerCompleted @ thread: 15288
-------------
Main @ thread: 11064
bgw1_DoWork @ thread: 5140
bgw1_RunWorkerCompleted @ thread: 12584
bgw2_DoWork @ thread: 12584
bgw2_RunWorkerCompleted @ thread: 17260
-------------
Main @ thread: 11064
bgw1_DoWork @ thread: 15288
bgw1_RunWorkerCompleted @ thread: 5140
bgw2_DoWork @ thread: 5140
bgw2_RunWorkerCompleted @ thread: 12584
-------------
Main @ thread: 11064
bgw1_DoWork @ thread: 15288
bgw1_RunWorkerCompleted @ thread: 17260
bgw2_DoWork @ thread: 17260
bgw2_RunWorkerCompleted @ thread: 12584
测试代码:
class Program
{
static void Main(string[] args)
{
for (Int32 i = 0; i < 5; i++)
{
Console.WriteLine("-------------");
Console.WriteLine("Main @ thread: " + GetCurrentWin32ThreadId());
BackgroundWorker bgw1 = new BackgroundWorker();
bgw1.DoWork += bgw1_DoWork;
bgw1.RunWorkerCompleted += bgw1_RunWorkerCompleted;
bgw1.RunWorkerAsync();
Console.ReadKey();
}
}
static void bgw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine("bgw1_RunWorkerCompleted @ thread: " + GetCurrentWin32ThreadId());
BackgroundWorker bgw2 = new BackgroundWorker();
bgw2.DoWork += bgw2_DoWork;
bgw2.RunWorkerCompleted += bgw2_RunWorkerCompleted;
bgw2.RunWorkerAsync();
}
static void bgw1_DoWork(object sender, DoWorkEventArgs e)
{
Console.WriteLine("bgw1_DoWork @ thread: " + GetCurrentWin32ThreadId());
//BackgroundWorker bgw2 = new BackgroundWorker();
//bgw2.DoWork += bgw2_DoWork;
//bgw2.RunWorkerCompleted += bgw2_RunWorkerCompleted;
//bgw2.RunWorkerAsync();
Thread.Sleep(1000);
}
static void bgw2_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine("bgw2_RunWorkerCompleted @ thread: " + GetCurrentWin32ThreadId());
}
static void bgw2_DoWork(object sender, DoWorkEventArgs e)
{
Console.WriteLine("bgw2_DoWork @ thread: " + GetCurrentWin32ThreadId());
Thread.Sleep(1000);
}
[DllImport("Kernel32", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)]
public static extern Int32 GetCurrentWin32ThreadId();
}
加 1
一些参考资料:
来自 here:
BackgroundWorker is the same thing as a thread pool thread. It adds
the ability to run events on the UI thread...
您发现程序的 UI 线程有一些特别之处。肯定有,它做了一些典型程序中其他线程从未做过的事情。正如您所发现的,不是线程池线程,也不是控制台模式应用程序中的主线程。它调用 Application.Run()
.
您喜欢 BGW 的地方在于它能够在 UI 线程上 运行ning 代码。 运行 特定 线程上的代码听起来应该很简单。然而它不是,一个线程总是 忙于执行代码,你不能随意打断它正在做的任何事情并让它运行 做别的事情。那将是可怕错误的来源,您有时 运行 也会在 UI 代码中遇到这种错误。 重入错误,与线程竞争错误一样难以解决。
必要的是线程合作并明确表示它处于安全状态并准备好执行某些代码。这是一个普遍的问题,在非UI场景中也会出现。线程要解决producer-consumer problem.
该问题的通用解决方案是从线程安全队列中获取数据的循环。该循环的通用名称是 "message loop"。在后来的 UI 框架中,术语 "dispatcher loop" 变得很普遍。该循环由 Application.Run() 开始。您看不到队列,它内置于 OS 中。但是您往往会在堆栈跟踪中看到从队列中检索消息的函数,它是 GetMessage()。当您解决非 UI 线程的问题时,您可以随意命名它,您通常会使用 ConcurrentQueue<T>
class 来实现队列。
值得注意的是为什么UI线程总是要解决那个问题。大块代码的共同点是很难使此类代码线程安全。即使是很小的代码块也很难做到线程安全。像 List<T>
这样简单的东西不是例如,你必须用 lock
语句来填充你的代码以确保它的安全。这通常效果很好,但您没有希望为 UI 代码正确地执行此操作。最大的问题是有很多代码你看不到,甚至不知道也无法更改以注入锁。确保安全的唯一方法是确保您只从正确的线程进行调用。 BGW 帮你做什么
同样值得注意的是,这对您的编程方式产生了巨大的影响。 GUI 程序必须将代码放入事件处理程序(由调度程序循环触发)并确保此类代码的执行时间不会太长。在调度程序循环中花费太长时间,阻止等待消息被发送。您总是可以看出,UI 冻结,绘画不再发生并且用户输入没有响应。控制台模式应用程序非常简单,非常。控制台不需要调度程序循环,与 GUI 不同,它非常简单,OS 会在控制台调用本身周围加锁。它总是可以重新绘制,您写入控制台缓冲区,另一个进程 (conhost.exe) 使用它重新绘制控制台 window。当然,停止控制台响应仍然很常见,但用户并不期望它保持响应。 Ctrl+C 和关闭按钮由 OS 而不是程序处理。
为了理解这一切的冗长介绍,现在开始介绍使 BGW 正常工作的管道。 BGW 本身不知道程序中的哪个特定线程是指定的 UI 线程。正如您所发现的,您必须在 UI 线程上调用 RunWorkerAsync() 才能保证其事件 运行 在 UI 线程上发生。它自己也不知道如何将获取代码的消息发送到 UI 线程上的 运行。它需要特定于 UI 框架的 class 的帮助。 SynchronizationContext.Current 属性 包含对 class 对象的引用,BGW 在您调用 RunWorkerAsync() 时复制它,以便稍后可以使用它来调用其 Post()触发事件的方法。对于 Winforms 应用程序,class 是 WindowsFormsSynchronizationContext,其 Send() 和 Post() 方法使用 Control.Begin/Invoke()。对于 WPF 应用程序,它是 DispatcherSynchronizationContext,它使用 Dispatcher.Begin/Invoke。 属性 对于工作线程或控制台模式应用程序为 null,BGW 然后必须创建自己的 SynchronizationContext 对象。除了使用 Threadpool.QueueUserWorkItem().
什么也做不了
我想弄清楚 BGW 如何决定在 RunWorkerCompleted 处理程序完成后 运行 哪个线程。
我的初始测试使用 WinForm 应用程序:
在 UI 线程上,我开始 bgw1.RunWorkerAsync()
。然后我尝试在 2 个不同的地方从 bgw2.RunWorkerAsync()
到 bgw1
开始:
bgw1_DoWork()
方法- 或
bgw1_RunWorkerCompleted()
方法。
我最初的猜测是 BGW 应该记住它是在哪个线程上启动的,并且 return 该线程在其工作完成后执行 RunWorkerCompleted
事件处理程序。
但是测试结果很奇怪:
测试 1
如果我在 bgw1_RunWorkerCompleted()
中启动 bgw2.RunWorkerAsync()
,bgw2_RunWorkerCompleted()
总是在 UI 线程上执行。
UI @ thread: 9252
bgw1_DoWork @ thread: 7216
bgw1_RunWorkerCompleted @ thread: 9252 <------ ALWAYS same as UI thread 9252
bgw2_DoWork @ thread: 7216
bgw2_RunWorkerCompleted @ thread: 9252
bgw1_DoWork @ thread: 7216
bgw1_RunWorkerCompleted @ thread: 9252
bgw2_DoWork @ thread: 1976
bgw2_RunWorkerCompleted @ thread: 9252
bgw1_DoWork @ thread: 7216
bgw1_RunWorkerCompleted @ thread: 9252
bgw2_DoWork @ thread: 1976
bgw2_RunWorkerCompleted @ thread: 9252
bgw1_DoWork @ thread: 7216
bgw1_RunWorkerCompleted @ thread: 9252
bgw2_DoWork @ thread: 1976
bgw2_RunWorkerCompleted @ thread: 9252
bgw1_DoWork @ thread: 7216
bgw1_RunWorkerCompleted @ thread: 9252
bgw2_DoWork @ thread: 7216
bgw2_RunWorkerCompleted @ thread: 9252
测试 2
但是如果我在 bgw1_DoWork()
中启动 bgw2.RunWorkerAsync()
,我认为 bgw2
应该记住 bgw1.DoWork()
线程并且 bgw2_RunWorkerCompleted()
应该总是 return 使用 bgw1_DoWork()
线程。但实际上不是。
UI @ thread: 6352
bgw1_DoWork @ thread: 2472
bgw1_RunWorkerCompleted @ thread: 6352
bgw2_DoWork @ thread: 18308
bgw2_RunWorkerCompleted @ thread: 2472
bgw1_DoWork @ thread: 12060 <------- bgw1_DoWork
bgw1_RunWorkerCompleted @ thread: 6352
bgw2_DoWork @ thread: 8740
bgw2_RunWorkerCompleted @ thread: 12060 <------- SOME SAME AS bgw1_DoWork
bgw1_DoWork @ thread: 7028
bgw1_RunWorkerCompleted @ thread: 6352
bgw2_DoWork @ thread: 2640
bgw2_RunWorkerCompleted @ thread: 7028
bgw1_DoWork @ thread: 5572 <------- HERE is 5572
bgw1_RunWorkerCompleted @ thread: 6352
bgw2_DoWork @ thread: 32
bgw2_RunWorkerCompleted @ thread: 2640 <------- HERE is not 5572
bgw1_DoWork @ thread: 10924
bgw1_RunWorkerCompleted @ thread: 6352
bgw2_DoWork @ thread: 12932
bgw2_RunWorkerCompleted @ thread: 10924
那么,BGW是如何决定哪个线程运行完成事件的呢?
测试代码:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private BackgroundWorker bgw1;
private BackgroundWorker bgw2;
private void Form1_Load(object sender, EventArgs e)
{
this.textBox1.Text += "UI @ thread: " + GetCurrentWin32ThreadId() + Environment.NewLine;
bgw1 = new BackgroundWorker();
bgw1.DoWork += bgw1_DoWork;
bgw1.RunWorkerCompleted += bgw1_RunWorkerCompleted;
bgw2 = new BackgroundWorker();
bgw2.DoWork += bgw2_DoWork;
bgw2.RunWorkerCompleted += bgw2_RunWorkerCompleted;
}
void bgw1_DoWork(object sender, DoWorkEventArgs e)
{
Int32 tid = GetCurrentWin32ThreadId();
this.textBox1.Invoke(new MethodInvoker(() => { this.textBox1.Text += "bgw1_DoWork @ thread: " + tid + Environment.NewLine; })); //"invoked" on UI thread.
Thread.Sleep(1000);
//this.bgw2.RunWorkerAsync(); // <==== START bgw2 HERE
}
void bgw2_DoWork(object sender, DoWorkEventArgs e)
{
Int32 tid = GetCurrentWin32ThreadId();
this.textBox1.Invoke(new MethodInvoker(() => { this.textBox1.Text += "bgw2_DoWork @ thread: " + tid + Environment.NewLine; })); //"invoked" on UI thread.
Thread.Sleep(1000);
}
void bgw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//this will go back to the UI thread, too.
this.textBox1.Text += "bgw1_RunWorkerCompleted @ thread: " + GetCurrentWin32ThreadId() + Environment.NewLine;
this.bgw2.RunWorkerAsync(); // <==== OR START bgw2 HERE
}
void bgw2_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.textBox1.Text += "bgw2_RunWorkerCompleted @ thread: " + GetCurrentWin32ThreadId() + Environment.NewLine;
}
private void button1_Click(object sender, EventArgs e)
{
this.bgw1.RunWorkerAsync();
}
[DllImport("Kernel32", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)]
public static extern Int32 GetCurrentWin32ThreadId();
}
测试 3
然后我尝试使用控制台应用程序。虽然我仍然像测试 1 一样在 bgw1_RunWorkerCompleted()
中启动 bgw2.RunWorkerAsync()
,但 bgw1
或 bgw2
都没有在主线程上完成。这与测试 1 非常不同。
我原以为这里的主线程是 UI 线程的 counterpart。 但似乎 UI 线程与控制台主线程的处理方式不同。
-------------
Main @ thread: 11064
bgw1_DoWork @ thread: 15288
bgw1_RunWorkerCompleted @ thread: 17260
bgw2_DoWork @ thread: 17260
bgw2_RunWorkerCompleted @ thread: 15288
-------------
Main @ thread: 11064
bgw1_DoWork @ thread: 12584
bgw1_RunWorkerCompleted @ thread: 17260
bgw2_DoWork @ thread: 17260
bgw2_RunWorkerCompleted @ thread: 15288
-------------
Main @ thread: 11064
bgw1_DoWork @ thread: 5140
bgw1_RunWorkerCompleted @ thread: 12584
bgw2_DoWork @ thread: 12584
bgw2_RunWorkerCompleted @ thread: 17260
-------------
Main @ thread: 11064
bgw1_DoWork @ thread: 15288
bgw1_RunWorkerCompleted @ thread: 5140
bgw2_DoWork @ thread: 5140
bgw2_RunWorkerCompleted @ thread: 12584
-------------
Main @ thread: 11064
bgw1_DoWork @ thread: 15288
bgw1_RunWorkerCompleted @ thread: 17260
bgw2_DoWork @ thread: 17260
bgw2_RunWorkerCompleted @ thread: 12584
测试代码:
class Program
{
static void Main(string[] args)
{
for (Int32 i = 0; i < 5; i++)
{
Console.WriteLine("-------------");
Console.WriteLine("Main @ thread: " + GetCurrentWin32ThreadId());
BackgroundWorker bgw1 = new BackgroundWorker();
bgw1.DoWork += bgw1_DoWork;
bgw1.RunWorkerCompleted += bgw1_RunWorkerCompleted;
bgw1.RunWorkerAsync();
Console.ReadKey();
}
}
static void bgw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine("bgw1_RunWorkerCompleted @ thread: " + GetCurrentWin32ThreadId());
BackgroundWorker bgw2 = new BackgroundWorker();
bgw2.DoWork += bgw2_DoWork;
bgw2.RunWorkerCompleted += bgw2_RunWorkerCompleted;
bgw2.RunWorkerAsync();
}
static void bgw1_DoWork(object sender, DoWorkEventArgs e)
{
Console.WriteLine("bgw1_DoWork @ thread: " + GetCurrentWin32ThreadId());
//BackgroundWorker bgw2 = new BackgroundWorker();
//bgw2.DoWork += bgw2_DoWork;
//bgw2.RunWorkerCompleted += bgw2_RunWorkerCompleted;
//bgw2.RunWorkerAsync();
Thread.Sleep(1000);
}
static void bgw2_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine("bgw2_RunWorkerCompleted @ thread: " + GetCurrentWin32ThreadId());
}
static void bgw2_DoWork(object sender, DoWorkEventArgs e)
{
Console.WriteLine("bgw2_DoWork @ thread: " + GetCurrentWin32ThreadId());
Thread.Sleep(1000);
}
[DllImport("Kernel32", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)]
public static extern Int32 GetCurrentWin32ThreadId();
}
加 1
一些参考资料:
来自 here:
BackgroundWorker is the same thing as a thread pool thread. It adds the ability to run events on the UI thread...
您发现程序的 UI 线程有一些特别之处。肯定有,它做了一些典型程序中其他线程从未做过的事情。正如您所发现的,不是线程池线程,也不是控制台模式应用程序中的主线程。它调用 Application.Run()
.
您喜欢 BGW 的地方在于它能够在 UI 线程上 运行ning 代码。 运行 特定 线程上的代码听起来应该很简单。然而它不是,一个线程总是 忙于执行代码,你不能随意打断它正在做的任何事情并让它运行 做别的事情。那将是可怕错误的来源,您有时 运行 也会在 UI 代码中遇到这种错误。 重入错误,与线程竞争错误一样难以解决。
必要的是线程合作并明确表示它处于安全状态并准备好执行某些代码。这是一个普遍的问题,在非UI场景中也会出现。线程要解决producer-consumer problem.
该问题的通用解决方案是从线程安全队列中获取数据的循环。该循环的通用名称是 "message loop"。在后来的 UI 框架中,术语 "dispatcher loop" 变得很普遍。该循环由 Application.Run() 开始。您看不到队列,它内置于 OS 中。但是您往往会在堆栈跟踪中看到从队列中检索消息的函数,它是 GetMessage()。当您解决非 UI 线程的问题时,您可以随意命名它,您通常会使用 ConcurrentQueue<T>
class 来实现队列。
值得注意的是为什么UI线程总是要解决那个问题。大块代码的共同点是很难使此类代码线程安全。即使是很小的代码块也很难做到线程安全。像 List<T>
这样简单的东西不是例如,你必须用 lock
语句来填充你的代码以确保它的安全。这通常效果很好,但您没有希望为 UI 代码正确地执行此操作。最大的问题是有很多代码你看不到,甚至不知道也无法更改以注入锁。确保安全的唯一方法是确保您只从正确的线程进行调用。 BGW 帮你做什么
同样值得注意的是,这对您的编程方式产生了巨大的影响。 GUI 程序必须将代码放入事件处理程序(由调度程序循环触发)并确保此类代码的执行时间不会太长。在调度程序循环中花费太长时间,阻止等待消息被发送。您总是可以看出,UI 冻结,绘画不再发生并且用户输入没有响应。控制台模式应用程序非常简单,非常。控制台不需要调度程序循环,与 GUI 不同,它非常简单,OS 会在控制台调用本身周围加锁。它总是可以重新绘制,您写入控制台缓冲区,另一个进程 (conhost.exe) 使用它重新绘制控制台 window。当然,停止控制台响应仍然很常见,但用户并不期望它保持响应。 Ctrl+C 和关闭按钮由 OS 而不是程序处理。
为了理解这一切的冗长介绍,现在开始介绍使 BGW 正常工作的管道。 BGW 本身不知道程序中的哪个特定线程是指定的 UI 线程。正如您所发现的,您必须在 UI 线程上调用 RunWorkerAsync() 才能保证其事件 运行 在 UI 线程上发生。它自己也不知道如何将获取代码的消息发送到 UI 线程上的 运行。它需要特定于 UI 框架的 class 的帮助。 SynchronizationContext.Current 属性 包含对 class 对象的引用,BGW 在您调用 RunWorkerAsync() 时复制它,以便稍后可以使用它来调用其 Post()触发事件的方法。对于 Winforms 应用程序,class 是 WindowsFormsSynchronizationContext,其 Send() 和 Post() 方法使用 Control.Begin/Invoke()。对于 WPF 应用程序,它是 DispatcherSynchronizationContext,它使用 Dispatcher.Begin/Invoke。 属性 对于工作线程或控制台模式应用程序为 null,BGW 然后必须创建自己的 SynchronizationContext 对象。除了使用 Threadpool.QueueUserWorkItem().
什么也做不了