OP:
How to notify thread from other class in C#...And after PreprocessSmth(-10.0, 10.0); I want to notify calcThread that it can start now.
So first a review. If we examine your original code:
Thread procThread = new Thread(() => preproc.Start());
procThread.Start();
Thread calcThread = new Thread(() => calc.Start());
calcThread.Start();
Thread displayThread = new Thread(() => display.Start());
displayThread.Start();
...we can see that three (3) Thread
s were created to drive preproc
, calc
and display
and all are in a running state. I understand that you want to notify calcThread
that it may begin once PreprocessSmth
has completed.
One easy way that goes hand-in-hand with old school Thread
programming is Windows Synchronization Objects, a common feature to most operating systems.
MSDN has this to say on the subject (my emphasis):
A synchronization object is an object whose handle can be specified in one of the wait functions to coordinate the execution of multiple threads. More than one process can have a handle to the same synchronization object, making interprocess synchronization possible. Tell me more...
...and NET (my emphasis):
.NET provides a range of types that you can use to synchronize access to a shared resource or coordinate thread interaction...Multiple .NET synchronization primitives derive from the System.Threading.WaitHandle
class, which encapsulates a native operating system synchronization handle and uses a signaling mechanism for thread interaction. Tell me more...
...specifically:
System.Threading.ManualResetEvent
, which derives from EventWaitHandle
and, when signaled, stays in a signaled state until the Reset
method is called. Tell me more...
The easiest way to think of a ManualResetEvent
is like a bathroom tap. It can be initially off but if turned on the tap will stay that way until someone in your household turns it off...that or when the local city council cuts your water supply for unpaid bills.
With that in mind, let's introduce the ManualResetEvent
to your code like so:
class Program
{
#region Statics
static void Main(string[] args)
{
Logger.WriteLine($"Inside Main, thread ID is {Environment.CurrentManagedThreadId}");
// initial state is NOT set (i.e. the "bathroom tap" is off)
var goodToGoEvent = new ManualResetEvent(false); // <--- NEW
var preproc = new PreProc();
var procThread = new Thread(() => preproc.Start(goodToGoEvent)); // pass in event
procThread.Start();
var calc = new Calc();
var calcThread = new Thread(() => calc.Start(goodToGoEvent)); // pass in event
calcThread.Start();
var display = new Display();
var displayThread = new Thread(() => display.Start());
displayThread.Start();
// wait for Calc to finish
Logger.WriteLine("Main thread - waiting for Calc thread to complete...");
calcThread.Join();
Logger.WriteLine("Main thread - waiting for Calc thread to complete...OK");
Logger.WriteLine("Press any key to exit");
Console.ReadKey();
}
#endregion
}
internal class Display
{
#region Methods
public void Start()
{
// NOP
}
#endregion
}
internal class Calc
{
#region Methods
/// <summary>
/// Starts the specified good to go event.
/// </summary>
/// <param name="goodToGoEvent">The event to wait on that indicates "good-to-go".</param>
public void Start(ManualResetEvent goodToGoEvent)
{
Logger.WriteLine("Calc - waiting for good-to-go event to be signaled...");
goodToGoEvent.WaitOne(); // block current thread until event is signaled
Logger.WriteLine("Calc - waiting for good-to-go event to be signaled...OK");
// now that PreProc is complete do what needs to be done here
}
#endregion
}
internal class PreProc
{
#region Fields
private object data = new object();
#endregion
#region Methods
/// <summary>
/// Starts the specified good to go event.
/// </summary>
/// <param name="goodToGoEvent">the event to signal once processing is complete.</param>
public void Start(ManualResetEvent goodToGoEvent)
{
//lock (data) // <--- not necessary in this example unless other threads will modify data concurrently
{
PreprocessSmth(-10.0, 10.0);
}
//I want to notify next thread here that preprocess ended and now next thread can run
Logger.WriteLine("PreProc - setting goodToGoEvent to signaled");
goodToGoEvent.Set(); // let other threads know that preprocess has ended
//TODO: data can be calculated now
SaveResultsToDatabase();
DoSomethingElseThatTakesTime();
}
private void DoSomethingElseThatTakesTime()
{
// NOP
}
private void PreprocessSmth(double d, double d1)
{
Logger.WriteLine("PreProc - Preprocessing...(sleeping 5 secs)");
Thread.Sleep(TimeSpan.FromSeconds(5)); // simulate lengthy operation
Logger.WriteLine("PreProc - Preprocessing...OK");
}
private void SaveResultsToDatabase()
{
// NOP
}
#endregion
}
public static class Logger
{
#region Static fields
private static readonly object Locker = new object();
#endregion
#region Statics
public static void WriteLine(string message)
{
lock (Locker)
{
Console.WriteLine($"{DateTime.Now:hh:mm:ss.ff} [{Environment.CurrentManagedThreadId}] {message}");
}
}
#endregion
}
In my code I intoduce a new variable goodToGoEvent
of type ManualResetEvent
which is passed to both preproc.Start
and calc.Start
. It is the job of preproc
to Set
the event whilst calc
Wait
s for it. Think of it like a set of traffic lights.
Here is the code in action, the numbers shown in square brackets ([ ]) is the corresponding thread ID:

Contemporary Threading APIs
Depending on which .NET version you are using/limited to, you might want to check out Microsoft's Task Parallel Library, Task
and how it is all wrapped up in a ribbon with async/await
(best experienced in .NET 4.5+).
If I may be so bold:
async/await is the contemporary and easiest way to do async programming whether or not the job is I/O-bound; whether or not the Task requires a thread. Gratuitous Self Promotion
More