I am trying to figure out how BGW decides which thread to run the RunWorkerCompleted handler when its work is done.
My initial test uses a WinForm application:
On the UI thread, I start bgw1.RunWorkerAsync()
. Then I tried to start bgw2.RunWorkerAsync()
through bgw1
in 2 different places:
bgw1_DoWork()
method- or
bgw1_RunWorkerCompleted()
method.
My initial guess is BGW should remember which thread it is started on and return to that thread to execute the RunWorkerCompleted
event handler when its work is done.
But the test result is strange:
Test 1
If I start the bgw2.RunWorkerAsync()
in bgw1_RunWorkerCompleted()
, the bgw2_RunWorkerCompleted()
is always executed on the UI thread.
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
But if I start the bgw2.RunWorkerAsync()
in bgw1_DoWork()
, I think bgw2
should remember the bgw1.DoWork()
thread and the bgw2_RunWorkerCompleted()
should always return to use bgw1_DoWork()
thread. But actually not.
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
So, how does BGW decide which thread to run the completed event?
Test code:
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
Then I tried with a console application. Though I still start the bgw2.RunWorkerAsync()
in bgw1_RunWorkerCompleted()
just like Test 1, neither of bgw1
or bgw2
complete on the Main thread. This is very different from Test 1.
I was expecting the main thread here is the counterpart of the UI thread. But it seems UI thread is treated differently from a console main thread.
-------------
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
Test code:
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
Some references:
From here:
BackgroundWorker is the same thing as a thread pool thread. It adds the ability to run events on the UI thread...