我试图弄清楚 BGW 在工作完成后如何决定运行 RunWorkerCompleted 处理程序的线程。
我的初始测试使用 WinForm 应用程序:
在 UI 线程上,我开始bgw1.RunWorkerAsync()
。然后我尝试开始bgw2.RunWorkerAsync()
通过bgw1
在两个不同的地方:
-
bgw1_DoWork()
method
- or
bgw1_RunWorkerCompleted()
method.
我最初的猜测是 BGW 应该记住它是在哪个线程上启动的,并返回到该线程来执行RunWorkerCompleted
当其工作完成时的事件处理程序。
但测试结果很奇怪:
Test 1
如果我开始bgw2.RunWorkerAsync()
in bgw1_RunWorkerCompleted()
, the 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
Test 2
但如果我开始bgw2.RunWorkerAsync()
in bgw1_DoWork()
, 我认为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();
}
Test 3
然后我尝试使用控制台应用程序。虽然我仍然开始bgw2.RunWorkerAsync()
in bgw1_RunWorkerCompleted()
就像测试 1 一样,两者都不是 bgw1
or 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
一些参考:
From here https://stackoverflow.com/questions/17008550/thread-threadpool-or-backgroundworker:
BackgroundWorker 和线程池线程是一样的东西。它添加了
在 UI 线程上运行事件的能力...