我在研究BGW是如何决定在完成工作后运行哪个线程的RunWorkerCompleted处理程序。
我的初始测试使用一个WinForm应用程序:
在UI线程上,我启动bgw1.RunWorkerAsync()。然后我尝试在两个不同的地方通过bgw1启动bgw2.RunWorkerAsync():
1. bgw1_DoWork() 方法 2. 或 bgw1_RunWorkerCompleted()方法。
我最初的猜测是BGW应该记住它是在哪个线程上启动的,并在完成工作后返回该线程以执行RunWorkerCompleted事件处理程序。
但测试结果很奇怪:
测试1
如果我在bgw1_RunWorkerCompleted()中启动bgw2.RunWorkerAsync(),则bgw2_RunWorkerCompleted()始终在UI线程上执行。
我的初始测试使用一个WinForm应用程序:
在UI线程上,我启动bgw1.RunWorkerAsync()。然后我尝试在两个不同的地方通过bgw1启动bgw2.RunWorkerAsync():
1. bgw1_DoWork() 方法 2. 或 bgw1_RunWorkerCompleted()方法。
我最初的猜测是BGW应该记住它是在哪个线程上启动的,并在完成工作后返回该线程以执行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()
应该始终返回使用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线程的对应物。但是似乎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();
}
ADD 1
一些参考资料:
来自这里:
BackgroundWorker与线程池线程相同。它添加了在UI线程上运行事件的功能...
BackgroundWorker
的 参考源代码? - Bradley UffnerAsyncOperation.Post
传递,该方法通过由AsyncOperationManager
创建的SynchronizationContext
传递,使用应用程序的SynchronizationContext.Current
SyncronizationContext
。 - Bradley UffnerThreadPool.QueueUserWorkItem
,从那里开始就变得复杂了。 - Bradley Uffner