Efficiency driven developer. Ex-Microsoft (ADX, AATP, ATA). Ex-Aorato (Acquired by Microsoft).

profile for i3arnon on Stack Overflow
© Bar Arnon
  • July 2, 2015 | Task.Run, Async/Await, TaskCreationOptions, Task.Factory.StartNew | comments | Edit
  • LongRunning Is Useless For Task.Run With Async/Await
  • Back in the olden days of .NET 4.0 we didn’t have Task.Run. All we had to start a task was the complicated Task.Factory.StartNew. Among its parameters there’s a TaskCreationOptions often used to specify TaskCreationOptions.LongRunning. That flag gives TPL a hint that the task you’re about to execute will be longer than usual.

    Nowadays with .NET 4.5 and above we mostly use the simpler and safer Task.Run but it isn’t uncommon to wonder how do you pass TaskCreationOptions.LongRunning as a parameter like we used to do with Task.Factory.StartNew.

    The answer is that you can’t. This of course isn’t limited just to TaskCreationOptions.LongRunning. You can’t pass any of the TaskCreationOptions values. However most of them (like TaskCreationOptions.AttachedToParent) are there for extremely esoteric cases while TaskCreationOptions.LongRunning is there for your run-of-the-mill long running task.

    An often suggested workaround is to go back to Task.Factory.StartNew, which is perfectly fine for synchronous delegates (i.e. Action, Func<T>), however for asynchronous delegates (i.e. Func<Task>, Func<Task<T>>) there’s the whole Task<Task> confusion. Since Task.Factory.StartNew doesn’t have specific overloads for async/await asynchronous delegates map to the Func<T> where T is a Task. That makes the return value a Task<T> where T is a Task, hence Task<Task>.

    The .NET team anticipated this issue and it can be easily solved by using TaskExtensions.Unwrap (which is the accepted answer on the relevant Stack Overflow question):

    Task<Task> task = Task.Factory.StartNew(async () =>
    {
        while (IsEnabled)
        {
            await FooAsync();
            await Task.Delay(TimeSpan.FromSeconds(10));
        }
    }, TaskCreationOptions.LongRunning);
    
    Task actualTask = task.Unwrap();
    

    However that hides the actual issue which is:

    Task.Run with TaskCreationOptions.LongRunning doesn’t make sense for async/await.

    The internal implementation creates a new dedicated thread when you use TaskCreationOptions.LongRunning. Here’s the code for ThreadPoolTaskScheduler.QueueTask:

    protected internal override void QueueTask(Task task)
    {
        if ((task.Options & TaskCreationOptions.LongRunning) != 0)
        {
            // Run LongRunning tasks on their own dedicated thread.
            Thread thread = new Thread(s_longRunningThreadWork);
            thread.IsBackground = true; // Keep this thread from blocking process shutdown
            thread.Start(task);
        }
        else
        {
            // Normal handling for non-LongRunning tasks.
            bool forceToGlobalQueue = (task.Options & TaskCreationOptions.PreferFairness) != 0;
            ThreadPool.UnsafeQueueCustomWorkItem(task, forceToGlobalQueue);
        }
    }
    

    But when an async method reaches an await for an uncompleted task the thread it’s running on is released. When the task is completed the rest will be scheduled again, this time on a different ThreadPool thread. That means that we created a new thread needlessly. This doesn’t only waste time for the developer but also hurts performance as creating new threads is costly (otherwise we wouldn’t need the ThreadPool in the first place).

    So, if you ask yourself how to use Task.Run with TaskCreationOptions.LongRunning when your delegate is asynchronous, save yourself and your application some time and keep using Task.Run as it is:

    Task task = Task.Run(async () =>
    {
        while (IsEnabled)
        {
            await FooAsync();
            await Task.Delay(TimeSpan.FromSeconds(10));
        }
    });