It can be achieved, among other options, by taking advantage of Window's native event loop.
Following code is a POC for the same and it addresses all the 3 points you have mentioned. But note that it is just a POC. It is not type safe and it uses Delegate.DynamicInvoke which can be slow but it proves the concept nevertheless.
public static class EventLoop { private class EventTask { public EventTask(Delegate taskHandler) : this(taskHandler, null) {} public EventTask(Delegate taskHandler, Delegate callback) { TaskHandler = taskHandler; Callback = callback; } private Delegate Callback {get; set;} private Delegate TaskHandler {get; set;} public void Invoke(object param) { object[] paramArr = null; if (param.GetType().Equals(typeof(object[]))) { paramArr = (object[]) param; //So that DynamicInvoke does not complain } object res = null; if (TaskHandler != null) { if (paramArr != null) { res = TaskHandler.DynamicInvoke(paramArr); } else { res = TaskHandler.DynamicInvoke(param); } } if (Callback != null) { EnqueueSyncTask(Callback, res); } } } private static WindowsFormsSynchronizationContext _syncContext; public static void Run(Action<string[]> mainProc, string[] args) { //You need to reference System.Windows.Forms _syncContext = new WindowsFormsSynchronizationContext(); EnqueueSyncTask(mainProc, args); Application.Run(); } public static void EnqueueSyncTask(Delegate taskHandler, object param) { //All these tasks will run one-by-one in order on Main thread //either on call of Application.DoEvenets or when Main thread becomes idle _syncContext.Post(new EventTask(taskHandler).Invoke, param); } public static void EnqueueAsyncTask(Delegate taskHandler, object param, Delegate callback) { //En-queue on .Net Thread Pool ThreadPool.QueueUserWorkItem(new EventTask(taskHandler, callback).Invoke, param); } }
Client Code:
[STAThread] static void Main(string[] args) { Thread.CurrentThread.Name = "MAIN THREAD"; Console.WriteLine("Method Main: " + Thread.CurrentThread.Name); EventLoop.Run(MainProc, args); } static void MainProc(string[] args) { Console.WriteLine("Method MainProc: " + Thread.CurrentThread.Name); Console.WriteLine("Queuing Long Running Task..."); EventLoop.EnqueueAsyncTask(new Func<int,int,int>(LongCalculation), new object[]{5,6}, new Action<int>(PrintResult)); Console.WriteLine("Queued Long Running Task"); Thread.Sleep(400); //Do more work EventLoop.EnqueueAsyncTask(new Func<int, int, int>(LongCalculation), new object[] { 15, 16 }, new Action<int>(PrintResult)); Thread.Sleep(150); //Do some more work but within this time 2nd task is not able to complete, meanwhile 1st task completes //Long running Tasks will run in background but callback will be executed only when Main thread becomes idle //To execute the callbacks before that, call Application.DoEvents Application.DoEvents(); //PrintResult for 1st task as 2nd is not yet complete Console.WriteLine("Method MainProc: Working over-time!!!!"); Thread.Sleep(500); //After this sleep, 2nd Task's print will also be called as Main thread will become idle } static int LongCalculation(int a, int b) { Console.WriteLine("Method LongCalculation, Is Thread Pool Thread: " + Thread.CurrentThread.IsThreadPoolThread); Console.WriteLine("Running Long Calculation"); Thread.Sleep(500); //long calc Console.WriteLine("completed Long Calculation"); return a + b; } static void PrintResult(int a) { Console.WriteLine("Method PrintResult: " + Thread.CurrentThread.Name); Console.WriteLine("Result: " + a); //Continue processing potentially queuing more long running tasks }
Output:
