I've figured out a way to switch threads in the middle of a method. The benefit of that seems super obvious to me: that's what everyone wants to do anyway, and splitting up methods, using callbacks, or lambdas, or whatever is just code bureaucracy to make it work.But if this is so obvious to me, Microsoft guys probably thought of that too, yet didn't implement it as the default way of doing things in TPL. This makes me nervous. These custom awaiters seem to run correctly, but maybe I just haven't hit the gotchas yet.What could possibly go wrong?public static class UsageTest{    public static async Task DoItTheUsualWay()    {        // update UI        Dictionary<string, string> cantUseVar = null; // have to initialize        await Task.Run(() =>        {            // do heavy lifting            var cantAccessVarOutsideTheLambda = new object();            cantUseVar = new Dictionary<string, string>();        });        // update UI    }    public static async Task UseTheCoolNewAwaiters()    {        // update UI        await GoToThreadPool.Instance;        // do heavy lifting        var smoothSailing = new object();        await GoToMainThread.Instance;        // update UI        smoothSailing.ToString();    }}public class GoToThreadPool : INotifyCompletion{    public static readonly GoToThreadPool Instance = new GoToThreadPool();    public bool IsCompleted    {        get { return Thread.CurrentThread.IsThreadPoolThread; }    }    public GoToThreadPool GetAwaiter()    {        return this;    }    public void GetResult() { }    public void OnCompleted(Action continuation)    {        ThreadPool.QueueUserWorkItem(o => continuation());    }}public class GoToMainThread : INotifyCompletion{    public static readonly GoToMainThread Instance = new GoToMainThread();    public bool IsCompleted    {        get { return Thread.CurrentThread == Application.Current.Dispatcher.Thread; }    }    public GoToMainThread GetAwaiter()    {        return this;    }    public void GetResult() { }    public void OnCompleted(Action continuation)    {        Application.Current.Dispatcher.BeginInvoke(continuation);    }}
Going to the thread pool and back using custom awaiters
c#;.net;async await;task parallel library
What you are after is yielding to a different thread, which has been covered in this question. One difference is that Andrew's code implements ICriticalNotifyCompletion vs you're implementing just INotifyCompletion. I haven't found any good documentation which one to prefer and what Critical exactly means. It may means that his code work's in some weird corner case scenarios where yours might not - hard to say. Most articles state that it's optional to implement the critical interface so it may not matter.The other difference is that Andrew's code doesn't provide an option to switch back plus he's using a struct while you are using a class (which is just a potential performance optimization)The main conclusion was that Microsoft didn't provide this out of the box since they felt it was prone to abuse.In general I prefer Andrews usage pattern of TaskEx.YieldToThreadPoolThread() over your more direct await GoToThreadPool.Instance since it's slightly more readable.So combining your two implementations you could end up with something like this:public static class TaskEx{    public static YieldToThreadAwaitable YieldToThreadPool()    {        return new YieldToThread(null);    }    public static YieldToThreadAwaitable YieldToMainThread()    {        return new YieldToThread(Application.Current.Dispatcher.Thread);    }    public static YieldToThreadAwaitable YieldToThread(Thread target)    {        return new YieldToThread(target);    }}public class YieldToThreadAwaitable : INotifyCompletion{    private Thread _Target;    public YieldToThreadAwaitable(Thread target)    {        Target = target;    }    public bool IsCompleted    {        get { return _Target != null ? Thread.CurrentThread == _Target : Thread.CurrentThread.IsThreadPoolThread; }    }    public YieldToThreadAwaitable GetAwaiter()    {        return this;    }    public void GetResult() { }    public void OnCompleted(Action continuation)    {        if (_Target == null)        {            ThreadPool.QueueUserWorkItem(o => continuation());        }        else        {            Dispatcher.FromThread(_Target).BeginInvoke(continuation);        }    }}