I'm running a number of time\CPU intensive processes (TimeExpensive type) one after another. The Main thread (A) starts TimeExpensive process in another thread (B) asynchronously and becomes inactive. On Completion, Thread B fires caller's completion handler synchronously and starts next TimeExpensive process in thread B. A new thread (C) is created but After starting C, B finishes. So for n processes, n threads are created and most of time, they don't co-exist.
One may wish its implementation in linear single threaded way, but TimeExpensive is implemented by third party and while it runs, it uses all system cores and runs for hours.
//This will run as console app
class Program
{
static void Main(string[] args)
{
new Program().StartJobs();
}
void StartJobs()
{
Main mainJob = new Main();
mainJob.MainCompletionEvent +=
new Action<object, EventArgs>(mainJob_MainCompletionEvent);
mainJob.Start();
}
void mainJob_MainCompletionEvent(object sender, EventArgs e)
{
//if(success) Environment.Exit(0);
}
}
class Main
{
int processCounter = 0;
public event Action<object, EventArgs> MainCompletionEvent;
public void Start()
{
//...do other important tasks here...
processCounter++;
TimeExpensive te = new TimeExpensive();
te.CompletionEvent += new Action(TimeExpensive_CompletionHandler);
Thread aThread = new Thread(te.Run);
aThread.IsBackground = false;
aThread.Name = "TimeExpensive Thread: " + processCounter;
aThread.Start();
}
void TimeExpensive_CompletionHandler()
{
Console.WriteLine("current Thread Name: " + Thread.CurrentThread.Name);
//Start another Process In Background if
if (processCounter < 5)
{
Start();
}
else
{
Console.ReadKey();
if (JobCompletionEvent != null)
JobCompletionEvent(this, new EventArgs());
}
}
}
class TimeExpensive
{
public event Action CompletionEvent;
public void Run()
{
//doing time expensive task
//...
//when finish Notify completion Handler...
if (CompletionEvent != null)
{
CompletionEvent();
}
}
}
//Output
current Thread Name: TimeExpensive Thread: 1
current Thread Name: TimeExpensive Thread: 2
current Thread Name: TimeExpensive Thread: 3
current Thread Name: TimeExpensive Thread: 4
current Thread Name: TimeExpensive Thread: 5
Above implementation mimics the behavior I described. The thing that is bugging me is the Event Handler running synchronously until the next thread starts and during this time, it's doing lots of tasks that it's not designed for.
Not sure if this is good, Is there a way I can go back to thread A while in Thread B's completion handler? or Should I better start event handler execution using another delegate.BeginInvoke?
I wish doing it with simple yet safe approach. Any help is greatly appreciated.
P.S I read lots of posts but no one was dealing well with this scenario.
Edit
Static main is added to show how to kick start this code in console app. Remember one can also create UI to start "Main" job. It'll definitely create BackgroundWorker thread to create mainJob object and run it. Thanks!
No, you don't have the plumbing in place to marshal a call from one thread to another. That kind of plumbing is provided by the main thread of a GUI app. Out of necessity, user interfaces are pretty fundamentally thread-unsafe. There are several implementation details of a UI thread that support such marshaling. It acts like a consumer in a typical implementation of a producer/consumer threading model.
Which requires a thread-safe queue and a loop in the consumer that reads the queue. You may recognize this as the message queue in a Windows GUI app. With the message loop that calls GetMessage to read notifications and act on them. Marshaling a call is now simple, you just post a message to the message queue, the UI thread reads it and executes the request. Posting is implemented by Control.BeginInvoke for Winforms and Dispatcher.BeginInvoke for WPF.
You certainly can implement this synchronization mechanism yourself. The .NET 4 BlockingCollection class makes it easy. But do keep in mind that you have to fundamentally alter the way thread A executes. Staying responsive to requests posted to the queue is important. The kind of problem that a class like BackgroundWorker tries to solve. Keep in mind that the GUI message loop exists because it is necessary, UI isn't thread-safe. A console app doesn't (typically) have the same kind of burden, the console is thread-safe.
The problem you are experiencing is due to how hard it is to do threading correctly. I coded your example with actors:
Which as output:
As you can see, it becomes a breeze to communicate across threads. If you library is a third-party library, then it is easy to replace
Async.Sleep(20)
with the invocation to the library.