Await/Async... a console app

await and async are a new keyword introduced in .Net 4.5 which neatly simplify asynchronous code block to look like a normal, synchronous code. But when I want to get started with a console app, I find these keywords have some strange specification… await can be used only in a method declared with async… An async method can only return Task derived instance, or void.

So there is no clean way to call an async method in a console app?? After reading through MSDN Help, noticed that most of related examples to these keywords are implemented in WPF, which begins the asynchronous call in an async event method (e.g. button1_click). So this new language feature is designed for their major frameworks (and for a library store they just introduced recently). However, I find a good piece of article explaining how to accomplish this. Apparently, console app is not natively supported (or ignored?). [1]

Of course, to call an async method in a console app, we need to use the basic Wait() of Task instance. But another interesting point from this article is SynchronizationContext, which is called in the article as “Task Scheduler” and this gives a sense of another abstract layer over .NET 4 TPL.

From the given example, creating two-thread synchronization context is easy. The key is that the thread that will be used to run the work item must be in the same context!

class DoubleThreadSyncCtx : SynchronizationContext {
        public static void Run(Func<Task> action) {
            var previousContext = Current;
            try {
                var context = new DoubleThreadSyncCtx();
                SetSynchronizationContext(context);

                var task = action();
                task.ContinueWith(delegate{
                    Console.WriteLine("End async run.");
                    context.complete();
                }, TaskScheduler.Default);

                context.runOnThread();

                task.GetAwaiter().GetResult();
            } finally {
                SetSynchronizationContext(previousContext);
            }
        }
        public override void Post(SendOrPostCallback d, object state) {
            tasks.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
        }
        void complete() {
            tasks.CompleteAdding();
        }
        void runOnThread(){
            Console.WriteLine("Begin on thread...");
            var myContext = Current;
            var thread1 = new Thread(delegate(object o){
                var previous = Current;
                try{
                    SetSynchronizationContext(myContext);
                    threadWork();
                }finally{
                    SetSynchronizationContext(previous);
                }
            });
            thread1.Start();
            
            Console.WriteLine("Thread 1 is {0}", thread1.ManagedThreadId);
            threadWork();
            thread1.Join();
            Console.WriteLine("End on thread...");
        }
        void threadWork(){
            Console.WriteLine("Thread {0} starts", Thread.CurrentThread.ManagedThreadId);
            KeyValuePair<SendOrPostCallback, object> workItem;
            while (tasks.TryTake(out workItem, Timeout.Infinite)) {
                workItem.Key(workItem.Value);
            }
            Console.WriteLine("Thread {0} ends", Thread.CurrentThread.ManagedThreadId);
        }
        readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> tasks = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
}

Another interesting article of the same author is Part 3, which deals with an async method that returns void. SynchronizationContext's OperationStarted() and OperationCompleted() are called for each void async method being invoked.

References


  1. Await, SynchronizationContext, and Console Apps (http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx) ↩︎