Threads - WaitForMultiObjects equivalent in C#

  • Thread starter Thread starter Hector Santos
  • Start date Start date
H

Hector Santos

For the longest, for my non-I/O based thread work, I used
WaitForMultiObjects (WFMO) as a way to wait for a set of worker
threads to complete, simpling by putting the thread handles in an
array and passing it to WFMO, for example:

// Wait (infinitely) for 10 threads to ALL complete.

HANDLE hThreads[10] = {0};

for(int i=0; i<10; i++) {
hThreads = CreateThread(.......);
}

WFMO(hThreads,10,TRUE,INFINITE);

I wish to do the same in C#, I can start the threads, but I don't see
a simple WFMO() idea.

Ideally, I wish to have a callback version of WFMO() so that an event
is called (i.e. a OnThreadsFinish() event) signally when all threads
are finished.

Note: I was able to come up with a method, but it seems there is a
more natural way in .NET. What I do is:

1) Create X BackgroundWorkers and put it into an array Workers[]

2) Then have a DoEvents loop:

int done = 0;
while (done < X)
{
done = 0;
for (int i=0; i < X; i++) {
if (!Workers.IsBusy) done++;
}
Application.DoEvents();
}

// THREADS WE DONE - do whatever

I only used a Thread Pool. But I don't see a function there to
determine when all the queued threads are complete.

Thanks
 
Hector Santos wrote :
For the longest, for my non-I/O based thread work, I used WaitForMultiObjects
(WFMO) as a way to wait for a set of worker threads to complete, simpling by
putting the thread handles in an array and passing it to WFMO, for example:

// Wait (infinitely) for 10 threads to ALL complete.

HANDLE hThreads[10] = {0};

for(int i=0; i<10; i++) {
hThreads = CreateThread(.......);
}

WFMO(hThreads,10,TRUE,INFINITE);

I wish to do the same in C#, I can start the threads, but I don't see a
simple WFMO() idea.

Ideally, I wish to have a callback version of WFMO() so that an event is
called (i.e. a OnThreadsFinish() event) signally when all threads are
finished.

Note: I was able to come up with a method, but it seems there is a more
natural way in .NET. What I do is:

1) Create X BackgroundWorkers and put it into an array Workers[]

2) Then have a DoEvents loop:

int done = 0;
while (done < X)
{
done = 0;
for (int i=0; i < X; i++) {
if (!Workers.IsBusy) done++;
}
Application.DoEvents();
}

// THREADS WE DONE - do whatever

I only used a Thread Pool. But I don't see a function there to determine
when all the queued threads are complete.

Thanks


I think you might be lookign for hte WaitHandle.WaitAll method?
 
Tom said:
I think you might be lookign for hte WaitHandle.WaitAll method?


Yes, thanks tom. I had missed it somehow in MSDN jumping all over the
place and got side tracked with MSDN examples with a MSDN comment
regarding "multi-tasking" behavior on single vs multi-processor/core
machines. I have to pencil that and go over that before I complete my
work.

But I had finally found it at this "Threading in C#" web site with
excellent summary background information for people like me. All
concepts consolidated right at the top! BOOK MARK IT! :)

http://www.albahari.com/threading/part3.aspx

MSDN threading pages also has some needed background information
regarding performance.

What I ended up doing is writing a class that spawns the dynamically
allocated workers with an additional optional thread to monitor the
completion and fire an OnMultiThreadComplete event. The default is to
just wait in the calling thread with a function that does the loop I
showed in my last post. That works nicely as well.

BTW, I found the ultimate MSDN Online Help GUI - CHROME!!! IE is just
too slow and it is superior with the "smart auto search" as you type.
Seems like GOOGLE indexed the entire MSDN content! Check it out!
Honestly, it isn't so bad now. THe IDE help simply sucks. But with
CHROME it is so fast that its like the old VC6 MSDN but better because
the #1 problem there was not being able to open many help page
instances. Any way, thats a different topic. :)


-- Coming soon, WCLEX - Wildcat! Live Exchange!
HLS
 
Hector Santos wrote on 6/1/2010 :
Yes, thanks tom. I had missed it somehow in MSDN jumping all over the place
and got side tracked with MSDN examples with a MSDN comment regarding
"multi-tasking" behavior on single vs multi-processor/core machines. I have
to pencil that and go over that before I complete my work.

But I had finally found it at this "Threading in C#" web site with excellent
summary background information for people like me. All concepts consolidated
right at the top! BOOK MARK IT! :)

http://www.albahari.com/threading/part3.aspx

MSDN threading pages also has some needed background information regarding
performance.

What I ended up doing is writing a class that spawns the dynamically
allocated workers with an additional optional thread to monitor the
completion and fire an OnMultiThreadComplete event. The default is to just
wait in the calling thread with a function that does the loop I showed in my
last post. That works nicely as well.

BTW, I found the ultimate MSDN Online Help GUI - CHROME!!! IE is just too
slow and it is superior with the "smart auto search" as you type. Seems like
GOOGLE indexed the entire MSDN content! Check it out! Honestly, it isn't so
bad now. THe IDE help simply sucks. But with CHROME it is so fast that its
like the old VC6 MSDN but better because the #1 problem there was not being
able to open many help page instances. Any way, thats a different topic. :)


-- Coming soon, WCLEX - Wildcat! Live Exchange!
HLS

I'm glad you got it worked out. As for using Chrome - no thanks. I'll
stick to firefox. May not be as fast, but IMHO, Google is the current
evil empire :)
 
Tom said:
Hector Santos wrote :
For the longest, for my non-I/O based thread work, I used
WaitForMultiObjects (WFMO) as a way to wait for a set of worker
threads to complete, simpling by putting the thread handles in an
array and passing it to WFMO, for example:

// Wait (infinitely) for 10 threads to ALL complete.

HANDLE hThreads[10] = {0};

for(int i=0; i<10; i++) {
hThreads = CreateThread(.......);
}

WFMO(hThreads,10,TRUE,INFINITE);

I wish to do the same in C#, I can start the threads, but I don't see
a simple WFMO() idea.

[...]


I think you might be lookign for hte WaitHandle.WaitAll method?


Hector probably would think that's the right equivalent to what he's
used to.

However, IMHO it is a good idea to get used to using the .NET-native
threading and synchronization techniques where applicable.

In this particular example, this means a few things:

• Don't block the GUI thread. In other words, the very design idea
of waiting on all the worker threads is fundamentally flawed.

• Use the features built into BackgroundWorker to track and respond
to changes in the active thread count.

• In other cases, use the Monitor class for cross-thread signaling.

The first two points go together, in that using the BackgroundWorker
correctly means that the GUI thread does not wind up blocked. Here's an
example:

void RunWorkers(int count)
{
for (int i = 0; i < count; i++)
{
BackgroundWorker worker = new BackgroundWorker();
int delay = i * 500;

worker.DoWork += (sender, e) =>
{
// dummy work
Thread.Sleep(delay);
};

worker.RunWorkerCompleted += (sender, e) =>
{
// Signal completion
if (--count == 0)
{
AllWorkersDone();
}
};

worker.RunWorkerAsync();
}
}

void AllWorkersDone()
{
// do whatever here
}

Note that the above assumes RunWorkers() is being called on the main GUI
thread. Which means that each RunWorkerCompleted event handler is also
executed on the main GUI thread. This has a couple of useful effects:

• The "count" variable, used directly in RunWorkers() and as a
captured variable in the RunWorkerCompleted event handlers, is only ever
used in a single thread, ensuring synchronization.

• The AllWorkersDone() method itself will be executed on the main GUI
thread, just as if the original method had gotten stuck in a loop
waiting for the threads to complete.

Of course, the big difference being that the main GUI thread doesn't
wind up blocked as in the original implementation.

Pete
 
Peter said:
void RunWorkers(int count)
{
for (int i = 0; i < count; i++)
{
BackgroundWorker worker = new BackgroundWorker();
int delay = i * 500;

worker.DoWork += (sender, e) =>
{
// dummy work
Thread.Sleep(delay);
};

worker.RunWorkerCompleted += (sender, e) =>
{
// Signal completion
if (--count == 0)
{
AllWorkersDone();
}
};

worker.RunWorkerAsync();
}
}

void AllWorkersDone()
{
// do whatever here
}

• The "count" variable, used directly in RunWorkers() and as a
captured variable in the RunWorkerCompleted event handlers, is only ever
used in a single thread, ensuring synchronization.


Hi Pete,

I appreciate the example in using the anonymous functions! Something
I intend to master in C# :)

Just a few small notes:

1) As a principle, using time for synchronization is a engineering
taboo in thread designs. A Sleep() is not simulating work because
there is no work being done and hence no other promotions of system
overhead context switching and/or interrupts. Even then, inevitably,
it will provide unexpected behavior when using a "random" or unknown
thread residence time.

2) Using the loop limit in this example as a thread count "signal" is
not good here. Any delay within the loop that is longer than any
thread residency time will alter the captured count variable.

To illustrate, change the code to add a delay between thread startup
to see what I mean:

int delay = i*500;
if (i == 1) {
// i.e, Simulate Preparation work
Thread.Sleep(100);
}

Overall, from a software engineering standpoint, avoid thread designs
based on time syncing and avoid using the common resource such as loop
limit as the watch variable here.

Two simple solutions - set the thread count outside the loop, or first
prepare the threads, much like a "Suspended" concept and then start it
after the preparation.

void RunWorkers(int count)
{
int threadCount = count;
for (int i = 0; i < count; i++)
{
BackgroundWorker worker = new BackgroundWorker();
int delay = i * 500;
if (i == 1) {
// i.e, Simulate Preparation work
Thread.Sleep(100);
}

worker.DoWork += (sender, e) =>
{
// dummy work
Thread.Sleep(delay);
};

worker.RunWorkerCompleted += (sender, e) =>
{
// Signal completion
if (--threadCount == 0)
{
AllWorkersDone();
}
};

worker.RunWorkerAsync();
}
}

or

void RunWorkers(int count)
{
// prepare threads

BackgroundWorker[] workers = new BackgroundWorker[count];
for (int i = 0; i < count; i++)
{
worker = new BackgroundWorker();
int delay = i * 500;
if (i == 1) {
// i.e, Simulate Preparation work
Thread.Sleep(100);
}

workerDoWork += (sender, e) =>
{
// dummy work
Thread.Sleep(delay);
};

worker.RunWorkerCompleted += (sender, e) =>
{
// Signal completion
if (--Count == 0)
{
AllWorkersDone();
}
};
}

// start threads
foreach (var w in workers) w.RunWorkerAsync();
}


Anyway, I see your main layout method and it was very good to
illustrate this using anonymous functions - learned a lot. Thanks.
 
Hector said:
[...]
Just a few small notes:

Hector, you have completely missed the point of the code example, both
in terms of what things in the code example are important, and in
comprehending how the example actually works.
1) As a principle, using time for synchronization is a engineering taboo
in thread designs. A Sleep() is not simulating work because there is no
work being done and hence no other promotions of system overhead context
switching and/or interrupts. Even then, inevitably, it will provide
unexpected behavior when using a "random" or unknown thread residence time.

As my comment that reads "dummy work" explains, the call to
Thread.Sleep() has nothing to do with the code example. It's there
simply to simulate the worker thread taking some time to do work. In a
real implementation, the call to Thread.Sleep() would not be present,
and its presence or lack of presence has no effect on whether the
technique I've illustrated works or not.

If you feel more comfortable replacing the call to Thread.Sleep() with a
busy loop, go right ahead. It doesn't change the validity of the code
example one bit, because the call to Thread.Sleep() has nothing to do
with what's being illustrated.
2) Using the loop limit in this example as a thread count "signal" is
not good here. Any delay within the loop that is longer than any thread
residency time will alter the captured count variable.

You are also mistaken on your second point. As I already pointed out,
the thread-count variable is only ever used in a single thread: the main
GUI thread. The RunWorkerCompleted event handlers cannot execute until
the method that is initializing the threads has returned, and only one
event handler at a time can execute, because they all execute on the
same thread.
To illustrate, change the code to add a delay between thread startup to
see what I mean:

int delay = i*500;
if (i == 1) {
// i.e, Simulate Preparation work
Thread.Sleep(100);
}

It would have been nice if you'd actually tried adding that code
yourself. Had you done so, you would have seen that it has no effect on
the outcome of the code, other than to delay it of course.
Overall, from a software engineering standpoint, avoid thread designs
based on time syncing and avoid using the common resource such as loop
limit as the watch variable here.

Again, there is nothing in my code that relies on "time synching". The
call to Thread.Sleep() is simply to simulate the worker thread doing
something. The code will work regardless of the time each worker thread
spends doing work.
Two simple solutions - set the thread count outside the loop, or first
prepare the threads, much like a "Suspended" concept and then start it
after the preparation.

Neither of those are required. They only add complexity, for no benefit.
[...]
Anyway, I see your main layout method and it was very good to illustrate
this using anonymous functions - learned a lot. Thanks.

I hope that you will go back and review the code example more carefully.
As I said at the outset, it is a good idea to become familiar with the
..NET-native techniques, and in this case that includes understanding
what BackgroundWorker actually does and how it works.

In particular, knowing that the BackgroundWorker events, except for
DoWork, always get raised on the same thread that created the
BackgroundWorker instance (assuming that thread is one with a
SynchronizationContext, such as a normal, Forms GUI thread) is critical
in understanding why the code example I posted is correct.

Without that understanding, one might get distracted nit-picking aspects
of the code that don't have anything to do with the actual example, and
making incorrect statements about why one believes the code doesn't work
as posted.

Pete
 
Hector said:
For the longest, for my non-I/O based thread work, I used
WaitForMultiObjects (WFMO) as a way to wait for a set of worker threads
to complete, simpling by putting the thread handles in an array and
passing it to WFMO, for example:

// Wait (infinitely) for 10 threads to ALL complete.

HANDLE hThreads[10] = {0};

for(int i=0; i<10; i++) {
hThreads = CreateThread(.......);
}

WFMO(hThreads,10,TRUE,INFINITE);

I wish to do the same in C#, I can start the threads, but I don't see a
simple WFMO() idea.

Ideally, I wish to have a callback version of WFMO() so that an event is
called (i.e. a OnThreadsFinish() event) signally when all threads are
finished.

Note: I was able to come up with a method, but it seems there is a more
natural way in .NET. What I do is:

1) Create X BackgroundWorkers and put it into an array Workers[]

2) Then have a DoEvents loop:

int done = 0;
while (done < X)
{
done = 0;
for (int i=0; i < X; i++) {
if (!Workers.IsBusy) done++;
}
Application.DoEvents();
}

// THREADS WE DONE - do whatever

I only used a Thread Pool. But I don't see a function there to determine
when all the queued threads are complete.

Thanks


A completely different approach would be to use Retlang.

The following illustrates what you're after:
http://code.google.com/p/retlang/wiki/SummationExample

HTH
 
Peter said:
Hector said:
[...]
Just a few small notes:

Hector, you have completely missed the point of the code example, both
in terms of what things in the code example are important, and in
comprehending how the example actually works.


Really? I think you are assuming too to be saying that.
As my comment that reads "dummy work" explains, the call to
Thread.Sleep() has nothing to do with the code example. It's there
simply to simulate the worker thread taking some time to do work. In a
real implementation, the call to Thread.Sleep() would not be present,
and its presence or lack of presence has no effect on whether the
technique I've illustrated works or not.


Its an important concept to not mix up SLEEP with ACTUAL THREAD WORK
when it come to system stabilization.
You are also mistaken on your second point. As I already pointed out,
the thread-count variable is only ever used in a single thread: the main
GUI thread. The RunWorkerCompleted event handlers cannot execute until
the method that is initializing the threads has returned, and only one
event handler at a time can execute, because they all execute on the
same thread.


It would have been nice if you'd actually tried adding that code
yourself. Had you done so, you would have seen that it has no effect on
the outcome of the code, other than to delay it of course.


I did. This following illustrates the contention issue with count as
I described.

using System;
using System.Text;
using System.ComponentModel;
using System.Threading;

namespace ConsoleApplet {

class CoBackgroundWorker : BackgroundWorker
{
public int tid = 0;
}
class WorkerJobs
{
void BeepDone()
{
Console.Beep(2000, 200);
Console.Beep(1000, 50);
}

public void RunWorkers(int count)
{
Console.WriteLine("* Starting {0} Threads",count);
for (int i = 0; i < count; i++)
{
CoBackgroundWorker worker = new CoBackgroundWorker();
worker.tid = i;

int delay = i*500;
if (i == 1) Thread.Sleep(100);

worker.DoWork += (sender, e) =>
{
// dummy work
CoBackgroundWorker w = sender as CoBackgroundWorker;
Console.WriteLine("- tid: {0} delay={2} count={3}",
w.tid, delay ,count);
Thread.Sleep(delay);
};

worker.RunWorkerCompleted += (sender, e) =>
{
CoBackgroundWorker w = sender as CoBackgroundWorker;
WorkerDone(w.tid, count);
if (--count == 0)
{
AllWorkersDone(w.tid);
}
};
worker.RunWorkerAsync();
}
}

void WorkerDone(int tid, int count)
{
Console.WriteLine("- tid: {0} Worker Done! count={1}",tid,
count);
}
void AllWorkersDone(int tid)
{
BeepDone();
Console.WriteLine("- tid: {0} All Workers Done!",tid);
}
}

class Program
{

static void Main(string[] args)
{
WorkerJobs jobs = new WorkerJobs();

Console.WriteLine("- Press ESCAPE to Exit");

jobs.RunWorkers(5);

while (true)
{
Thread.Sleep(50);
if (Console.KeyAvailable &&
Console.ReadKey().Key == ConsoleKey.Escape) {
break;
}
}

}
}
}
 
Peter said:
Again, there is nothing in my code that relies on "time synching". The
call to Thread.Sleep() is simply to simulate the worker thread doing
something. The code will work regardless of the time each worker thread
spends doing work.


The point is that a little testing yourself would of shown that the
using the count variable was not a good idea.

I naturally saw it even for I tried it to prove it was wrong because
as you indicated, in the world real there would be "real work" not the
poor man's sleep (which isn't any kind of simulated work whatsoever).
This is common mistake in thread designs.
Neither of those are required. They only add complexity, for no benefit.


But your example had a bug as I illustrated.
I hope that you will go back and review the code example more carefully.


I did even before I posted. But only tried it because it was obvious
there would a thread contention issue.

As I said at the outset, it is a good idea to become familiar with the
.NET-native techniques, and in this case that includes understanding
what BackgroundWorker actually does and how it works.


I doubt you even know which a common mistake you showed. If you want
to be respected, then show some respect yourself.
In particular, knowing that the BackgroundWorker events, except for
DoWork, always get raised on the same thread that created the
BackgroundWorker instance (assuming that thread is one with a
SynchronizationContext, such as a normal, Forms GUI thread) is critical
in understanding why the code example I posted is correct.


You see, you assumed that I was naive about thread, GUI designs or
designs in general I seriously doubt you can match my expertise in
this area. But that wasn't even the question - how GUI are blocks or
not. Yet, to show a solution, an able one, you did what experts
consider a common mistake in thread design 101.
Without that understanding, one might get distracted nit-picking aspects
of the code that don't have anything to do with the actual example, and
making incorrect statements about why one believes the code doesn't work
as posted.


Well, the fact was my statements are 100% correct and on the money,
and I am following up for the archives, just in case someone needs
something similar and uses your posted code as a framework.

You need to focus on what the questions and DO NOT PREASSUME too much
into what a poster knows or doesn't.

Thanks for your comments.
 
Hector said:
[...]
Hector, you have completely missed the point of the code example, both
in terms of what things in the code example are important, and in
comprehending how the example actually works.

Really? I think you are assuming too to be saying that.

I'm not assuming anything. I'm just looking at your replies.
[...]
Its an important concept to not mix up SLEEP with ACTUAL THREAD WORK
when it come to system stabilization.

Sure, but there's nothing about my example that does that
[...]
It would have been nice if you'd actually tried adding that code
yourself. Had you done so, you would have seen that it has no effect
on the outcome of the code, other than to delay it of course.


I did. This following illustrates the contention issue with count as I
described.

No, it doesn't. I stated several times that the BackgroundWorker
depends on a GUI thread (actually, any thread with a
SynchronizationContext, but in practice this is almost always a GUI thread).

Your example is a console application and as such is completely
irrelevant to my code example and my comments.

Pete
 
Hector said:
The point is that a little testing yourself would of shown that the
using the count variable was not a good idea.

Please go back and read my posts more carefully. You are not paying
attention.
I naturally saw it even for I tried it to prove it was wrong because as
you indicated, in the world real there would be "real work" not the poor
man's sleep (which isn't any kind of simulated work whatsoever). This is
common mistake in thread designs.

There's nothing about the call to Thread.Sleep() in my code example that
has anything to do with the correctness of the code. You can put
anything you want in the DoWork event handler, and the code works just
as well.
But your example had a bug as I illustrated.

There is no bug in my example.
I did even before I posted. But only tried it because it was obvious
there would a thread contention issue.

It is clear that you still have not sufficiently reviewed my posts. You
still misunderstand the point of the code example.
I doubt you even know which a common mistake you showed. If you want to
be respected, then show some respect yourself.

There is no mistake in my code. It works fine in the context in which
it's intended to be used.
You see, you assumed that I was naive about thread, GUI designs or
designs in general

I made no such assumption. I made an _observation_ that you have failed
(and still continue to fail) to read my posts carefully enough to
understand the point of the code example.
I seriously doubt you can match my expertise in this
area.

I'm not interested in a pissing match. You go do that on your own. All
of my statements are strictly about factual matters that can be easily
resolved simply by running the code.

But: you do need to pay attention. Until you do so, you will not have
any idea what I'm talking about.

Pete
 
Peter Duniho wrote:

There is no bug in my example.


You're in denial. I appreciate your input, but anyone worth his salt
will see that that using a loop limit variable with internal local
scope change is asking for trouble and would be contention in a real
application using that piece of code. Sleep or not sleep, I saw it
immediately. Its extremely common to see that sort of thing with
inexperienced developers in thread designs - ON ANY SYSTEM. But with
that latter point, I gave you benefit of the doubt thinking maybe the
..NET compiler translated it as a constant, after all, it is a p-code
compiler. That was not the case here and the simply injecting of one
line sleep (your idea of simulated "work" when here is no work at all)
proves the case. When there is real work, you have your classic
contention issues creeping up. That could of been anything.

I'll leave it at that.
 
Hector said:
Peter Duniho wrote:



You're in denial.

You are wearing blinders.
I appreciate your input, but anyone worth his salt
will see that that using a loop limit variable with internal local scope
change is asking for trouble and would be contention in a real
application using that piece of code.

I have explained numerous times why that is not the case.
Sleep or not sleep, I saw it immediately. [...]

You saw what you wanted to see, because you apparently care more about
your criticism of me than technical accuracy.

If and when you ever go back and read my posts with the care they
deserve, you will see how you are mistaken about your assessment of the
code.

Pete
 
Peter said:
Hector Santos wrote:


I have explained numerous times why that is not the case.


And I explained three times now that you produced a bad example of
thread synchronization.
Sleep or not sleep, I saw it immediately. [...]

You saw what you wanted to see, because you apparently care more about
your criticism of me than technical accuracy.


Exactly!

But if I am critical of you, it is your attitude to believe everyone
is naive to extend and provide information that wasn't required.
Again, all you showed here was the anonymous function - thats it!!
Everything else was already known since the annals of preempted OSes!
If and when you ever go back and read my posts with the care they
deserve, you will see how you are mistaken about your assessment of the
code.

Peter you said nothing but what you wanted to point out believing it
was important, yet it was trivial to the question. It added nothing
beyond extracting two things only:

- Your example of using anonymous functions here, which was good,
- but nonetheless, also YOUR BUG!

And that was simply by eyeballing it and then verifying what would
easly BREAK IT - and yet you went on a MAJOR ASSUMPTION that I didn't
try your code, and you went on with that incorrect assumption. The
fact is you got a BUG and you should be MAN enough to admit.

Everything else was trivial information which was (2nd time I told you
this) was not being asked for. You need to stop that. Do it with
others, not with me. The other fellas were to the point in replies.
You acting like everyone here is stupid. What you indicated was
nothing new NOR what was asked for. You need to stop that.
 
And that was simply by eyeballing it and then verifying what would
easly BREAK IT - and yet you went on a MAJOR ASSUMPTION that I didn't
try your code, and you went on with that incorrect assumption. The
fact is you got a BUG and you should be MAN enough to admit.
I may regret this, but I'm going to stick my neck into this, as honestly
I don't think either one of you is completely understanding the other
:) Hopefully I pull my neck back before I get it chopped off!

Hector - I think Peter's point is that yes, you came up with a situation
where Peter's code does not work. However, the situation is a case
where it was NEVER intended to work. You created the "bug" in a console
application - the BackgroundWorker (to my knowledge) is designed solely
to function within a Windows Forms application (maybe WPF?? Not sure on
that one - I'll just stick with Windows Forms since you used
Application.DoEvents() in your initial example).

The threading behaves VERY differently between the two environments. In
a Windows Forms environment, as your original code snippet suggests you
are with the Application.DoEvents call, the
BackgroundWorker.RunWorkerCompleted event is guaranteed to ONLY execute
on the UI thread, and thus by definition can only be executed one at a
time. Since, at the end of Peter's post, he indicated that his sample
assumes "RunWorkers" is ALSO on the UI thread, that means that
RunWorkers itself will have to complete prior to any invocations of the
RunWorkerCompleted occurring. So, the only place "count" ever gets
modified (or accessed, assuming DoWork does not also access it), is from
the UI thread itself, which ensures there is no
race-condition/contention issues. That negates the need to pull out a
separate variable "threadCount" since "count" cannot, given that code
sample, be modified prematurely.

If RunWorkerCompleted is executed off the UI thread (or, ultimately I
guess more importantly the thread which executed RunWorkers), then yes,
the concerns you brought up are valid. However, the entire purpose of
BackgroundWorker is to get around that problem in a UI environment.

If you take the code sample outside the context they are given, sure,
they can break - but that is true about most (all?) code :)

-Adam
 
Still, you promised to point out how I've misunderstood Hector, but I
didn't see anything in your post to point that out. If I have, an
independent observer such as yourself might have just the insight that
would be useful to me to understand that.

Hehe, fair enough - I did forget to go down that path. Got down into
the technical details of the issue and hit send when I was done with them :)

Not so much that you were misunderstanding him, but maybe not making it
as easy for him to understand you. I had to jump back and forth quite a
bit between posts trying to connect all the dots. Part of that may
simply be the inline quoting response. The various pieces of important
and relevant information get spread out across several different
places. That was what I hoped to accomplish by my "block" reply -
consolidating all of the information into a single place.

Normally I think the inline responses are a good way to address
different points of a topic, but this time it made it harder for some
reason...

Just my thoughts...

-Adam
 
Hector said:
[...] The fact is you
got a BUG and you should be MAN enough to admit.

You can claim there's a bug until you're blue in the face, it still
won't make it true. You have not, and cannot, demonstrate the code I
posted breaking in the context in which I specifically stated it was
intended to be used.

Hector believes this would be a demonstration: in the first iteration of
the main loop first thread is created, its function set, and spawned to
run. BUT, when its time for second iteration first thread might already
be finished. This in turn decrements count, which reduces the number of
threads that would be spawned.

In C++ world this would be the case because RunWorkerCompleted would run
from spawned thread's context, but in C# world it will be run from main
thread's context, AFTER all threads are already spawned.

So, really, there's no bug here, AFAICS.
 
It's useful to note that BackgroundWorker will work in _any_ thread
where there's a marshaling SynchronizationContext. That is, the
default SynchronizationContext doesn't actually marshal delegate
invocations from one thread to another, but the SynchronizationContext
found in a thread where Application.Run is running, or a WPF
dispatcher is running, will do that.

So by default, BackgroundWorker provides the full functionality of its
features in either a Forms or WPF application, provided it's created
in a GUI thread in that application. Furthermore, it is possible to
write one's own SynchronizationContext class that works in other
contexts. The important thing is that a SynchronizationContext is
available for BackgroundWorker to use that will marshal delegate
invocations onto the thread that created the BackgroundWorker. It
doesn't have to be a Forms, or even WPF, application.

As a side note - thank you for this little explanation... I've had more
than a couple cases come up where I was dealing with a 3rd party API
which, by their requirement, I could only create it's objects and make
calls on those objects from a single thread (no UI even involved in
these situations).

I ended up creating my own object which I closely related to a
"ThreadPool" that only had 1 thread in it processing items (I did not
want to make use / alter the system thread pool). I have no idea what
would have been involved in writing "one's own SynchronizationContext",
but I'm wondering if that's basically what I did, or if trying to do so
would have simplified anything...

Anyway, gave me something to go think about and read up more on.

Thanks,
Adam
 
Adam said:
As a side note - thank you for this little explanation... I've had more
than a couple cases come up where I was dealing with a 3rd party API
which, by their requirement, I could only create it's objects and make
calls on those objects from a single thread (no UI even involved in
these situations).

I ended up creating my own object which I closely related to a
"ThreadPool" that only had 1 thread in it processing items (I did not
want to make use / alter the system thread pool). I have no idea what
would have been involved in writing "one's own SynchronizationContext",
but I'm wondering if that's basically what I did, or if trying to do so
would have simplified anything...

Yes, the innards of a custom SynchronizationContext is likely to look a
lot like a thread pool with one thread.

The Forms version actually uses the thread's message queue, and I don't
actually know how the WPF one works. But those are special cases meant
to deal with other requirements those APIs have. A custom one usually
wouldn't be bound by those issues and could just use a regular work
queue with a single consuming thread.
Anyway, gave me something to go think about and read up more on.

I recall a discussion with someone else several months ago (maybe even a
year or more…I don't recall for sure), in which they wanted to write
their own SynchronizationContext.

It should not be _too_ involved, and if I recall correctly, they were
able to get theirs working. I think they might have even posted the
source code here.

If you're interested and if after looking you aren't able to find the
thread I'm talking about, follow up here and I'll take a look myself.

Pete
 
Back
Top