c# - How does BackgroundWorker decide on which thread to run the RunWorkerCompleted handler? -
i trying figure out how bgw decides thread run runworkercompleted handler when work done.
my initial test uses winform application:
on ui thread, start bgw1.runworkerasync()
. tried start bgw2.runworkerasync()
through bgw1
in 2 different places:
bgw1_dowork()
method- or
bgw1_runworkercompleted()
method.
my initial guess bgw should remember thread started on , return thread execute runworkercompleted
event handler when work done.
but test result strange:
test 1
if start bgw2.runworkerasync()
in bgw1_runworkercompleted()
, bgw2_runworkercompleted()
executed on ui thread.
ui @ thread: 9252 bgw1_dowork @ thread: 7216 bgw1_runworkercompleted @ thread: 9252 <------ same 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 start bgw2.runworkerasync()
in bgw1_dowork()
, think bgw2
should remember bgw1.dowork()
thread , bgw2_runworkercompleted()
should return use bgw1_dowork()
thread. 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 <------- same bgw1_dowork bgw1_dowork @ thread: 7028 bgw1_runworkercompleted @ thread: 6352 bgw2_dowork @ thread: 2640 bgw2_runworkercompleted @ thread: 7028 bgw1_dowork @ thread: 5572 <------- here 5572 bgw1_runworkercompleted @ thread: 6352 bgw2_dowork @ thread: 32 bgw2_runworkercompleted @ thread: 2640 <------- here not 5572 bgw1_dowork @ thread: 10924 bgw1_runworkercompleted @ thread: 6352 bgw2_dowork @ thread: 12932 bgw2_runworkercompleted @ thread: 10924
so, how bgw decide thread run 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 go 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 tried console application. though still start bgw2.runworkerasync()
in bgw1_runworkercompleted()
test 1, neither of bgw1
or bgw2
complete on main thread. different test 1.
i expecting main thread here counterpart of ui thread. but seems ui thread treated differently 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) { (int32 = 0; < 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 same thing thread pool thread. adds ability run events on ui thread...
you discovered there special ui thread of program. there is, no other thread in typical program ever does. found out, not threadpool thread , not main thread in console mode app. calls application.run()
.
what bgw is capable of running code on ui thread. running code on specific thread sounds ought simple. not, thread always busy executing code, cannot arbitrarily interrupt whatever doing , make run else. source of horrible bugs, kind of bug run in ui code well. re-entrancy bug, hard solve threading race bug.
what necessary thread co-operates , explicitly signals in safe state , ready execute code. universal problem occurs in non-ui scenarios. thread has solve producer-consumer problem.
the universal solution problem loop takes data thread-safe queue. common name loop "message loop". in later ui frameworks term "dispatcher loop" became common. loop gets started application.run(). cannot see queue, built os. tend see function retrieves message queue in stack traces, getmessage(). when solve problem non-ui threads named whatever preferred, you'd commonly use concurrentqueue<t>
class implement queue.
it worth noting why ui thread has solve problem. common large chunks of code is difficult make such code thread-safe. small chunks of code hard make thread-safe. simple list<t>
not example, have pepper code lock
statement make safe. works out well, have no hope of doing correctly ui code. biggest issue there lot of code cannot see, don't know , can't change inject lock. way make safe ensure ever make call correct thread. bgw helps do.
worth noting enormous impact has on way program. gui program has put code in event handlers (fired dispatcher loop) , make sure such code not take long execute. taking long gums dispatcher loop, preventing waiting messages getting dispatched. can tell, ui freezes painting no longer occurring , user input having no response. console mode app much, much simpler program. console not need dispatcher loop, unlike gui simple , os puts locks around console calls itself. can repaint, write console buffer , process (conhost.exe) uses repaint console window. still common stop console being responsive of course, user has no expectation stays responsive. ctrl+c , close button handled os, not program.
long introduction make sense of all, down plumbing makes bgw work. bgw has no idea specific thread in program anointed ui thread. found out, must call runworkerasync() on ui thread guarantee events runs on ui thread. has no idea how send message gets code run on ui thread. needs class specific ui framework. synchronizationcontext.current property contains reference object of class, bgw copies when call runworkerasync() can use later call post() method fire event. winforms app, class windowsformssynchronizationcontext, send() , post() methods uses control.begin/invoke(). wpf app dispatchersynchronizationcontext, uses dispatcher.begin/invoke. property null worker thread or console mode app, bgw has create own synchronizationcontext object. can't use threadpool.queueuserworkitem().
Comments
Post a Comment