I have 10 threads running and I want to control the signals such that I can tell when 5 threads signaled me, and be able to stop them from signaling on demand.
this is the code:
class A
{
static readonly Random r = new Random(DateTime.Now.Ticks.GetHashCode());
static int ID = 1;
int id;
public A() { id = ID++; }
public void SomeFunc(object o)
{
Thread.Sleep(r.Next(2000, 5000));
lock (o)
{
Console.WriteLine("A-{0} called Set!", id);
((EventWaitHandle)o).Set();
}
}
static void Main(string[] args)
{
AutoResetEvent are = new AutoResetEvent(false);
A[] arr = new A[10];
for (int i = 0; i < arr.Length; i++)
arr[i] = new A();
for (int i = 0; i < arr.Length; i++)
new Thread(arr[i].SomeFunc).Start(are);
int timesSetIsCalled = 0;
while (timesSetIsCalled < 5)
{
are.WaitOne();
timesSetIsCalled++;
}
Console.WriteLine("{0} threads called set", timesSetIsCalled);
Thread.Sleep(7000);
Console.WriteLine("{0} threads called set", timesSetIsCalled);
}
}
Considering N threads have already called Set function My questions are:
How can I prevent other threads (already running) from calling Set?
How can I continue to count the treads that called Set, after the while loop in the main? I have to count inside the lock area? if so, how can I send a ref variable to the thread in order to inc?
.
while (timesSetIsCalled < 5)
{
are.WaitOne();
timesSetIsCalled++;
}
- How can I continue to count the treads that called Set, after the while loop in the main? I have to count inside the lock area? if so, how can I send a ref variable to the thread in order to inc?
Only the threads that call Set()
can count the threads that call Set()
. You can't tell how many threads set the event from observing the event object outside the threads that set it.
As it happens, addressing that requires moving the counting behavior into the thread, and doing that also addresses your secondary concern of having this counting continue to occur even after you exit the while
loop.
Here is a version of your code that shows how you might do this:
class Program
{
static void Main(string[] args)
{
Random r = new Random();
WaitCounter counter = new WaitCounter();
A[] arr = new A[10];
for (int i = 0; i < arr.Length; i++)
arr[i] = new A(r.Next(2000, 5000), counter);
for (int i = 0; i < arr.Length; i++)
new Thread(arr[i].SomeFunc).Start();
while (counter.Counter < 5)
{
counter.Wait();
}
Console.WriteLine("{0} threads called set", counter.Counter);
Thread.Sleep(7000);
Console.WriteLine("{0} threads called set", counter.Counter);
}
}
class A
{
private static int ID = 1;
private static readonly Stopwatch sw = Stopwatch.StartNew();
private readonly int id;
private readonly int duration;
private readonly WaitCounter counter;
public A(int duration, WaitCounter counter)
{
id = ID++;
this.duration = duration;
this.counter = counter;
}
public void SomeFunc(object o)
{
Thread.Sleep(duration);
counter.Increment();
Console.WriteLine(
@"{0}-{1} incremented counter! Elapsed time: {2:mm\:ss\.fff}",
GetType().Name, id, sw.Elapsed);
}
}
class WaitCounter
{
private readonly AutoResetEvent _event = new AutoResetEvent(false);
private int _counter;
public int Counter { get { return _counter; } }
public void Increment()
{
Interlocked.Increment(ref _counter);
_event.Set();
}
public void Wait()
{
_event.WaitOne();
}
}
Notes:
Counter
class. This abstracts the counters needed to track the threads' progress, as well as the AutoResetEvent
used to synchronize the threads. Interlocked
is used for the incrementing; the event is set simply to signal to the main thread a change to at least one counter has been made. In this way, it doesn't matter how many times or how many counters are incremented, the main thread will always be able to tell exactly how many times that occurred.Random
instance out of the thread method itself, instead sharing it among all instances of your A
class. This is better than what you had before, but is still not quite correct: the Random
class is not thread-safe, and does rely on internal state to produce correct results. Using it concurrently can lead to non-random, or at least incorrectly random output. I address that here by having the main thread create and use the sole Random
object, passing values as needed to the thread objects before they actually start running.DateTime.Now.Ticks.GetHashCode()
as the seed for the Random
instance. The Random
class already defaults to using a seed based on the system clock.
- How can I prevent other threads (already running) from calling Set?
Is this still required, if your second question is addressed per the above? It seems like probably it's not.
But if it is, please post a new question that makes clear what you mean by this. There are lots of different ways threads can communicate with each other, and it's not really clear from your question whether you actually want to prevent the other threads from calling Set()
or if you just want to delay them, and in either case what specific condition will control whether this happens or not.