I have following piece of code
(preproc, calc and display are different classes)
Thread procThread = new Thread(() => preproc.Start());
procThread.Start();
Thread calcThread = new Thread(() => calc.Start());
calcThread.Start();
Thread displayThread = new Thread(() => display.Start());
displayThread.Start();
In the start method of preproc.Start()
I have three methods
lock (data)
{
PreprocessSmth(-10.0, 10.0);
}
//I want to notify next thread here that preprocess ended and now next thread can run
//TODO: data can be calculated now
SaveResultsToDatabase();
DoSomethingElseThatTakesTime();
And after PreprocessSmth(-10.0, 10.0);
I want to notify calcThread
that it can start now.
I am using lock
but I don't know if it guarantees what I want.
EDIT: Using TPL I did something like that. Is that good practice, or I messed up?
Main method
Task preprocTask = preproc.StartAsync();
Task calcTask = calc.StartAsync();
Task displayTask = Task.Run(() => display.Start());
Task.WaitAll(preprocTask, calcTask, displayTask);
Inside Preproc class StartAsync method
private Task StartAsync()
{
PreprocessSmth(-10.0, 10.0);
//TODO: data can be calculated now
Task save = SaveResultsToDatabaseAsync();
Task doSomething = DoSomethingElseThatTakesTimeAsync();
return Task.WhenAll(save, doSomething);
}
And Calc class StartAsync is almost same like Preproc StartAsync.
So the flow is like that: Main->preproc.StartAsync()->PreprocessSmth(), then asynchronously SavingToDb and DoSomething, ->calc.StartAsync()->CalcSmth() then SaveDataToDb asynchronously
etc...
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 fromEventWaitHandle
and, when signaled, stays in a signaled state until theReset
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:
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
Thank you so much for explanation and code sample! Your answer clarify that we can pass ManualResetEvent to both objects. May I ask you, what if in
display.Start()
I have similar situation, so one method indisplay
have to wait for one method incalc
. I created new eventdisplayEvent
, and passed it tocalc
anddisplay
. Is it right way to do?No problem. Yes if
display
depends oncalc
that is how you do it.