Task creation overhead

c# thread vs task performance
c# how many tasks is too many
c# start multiple tasks simultaneously
task whenall
async/await c#
task whenall httpclient
c# run multiple tasks in parallel
async/await performance c#

I am reading a book "Terrell R. - Concurrency in .NET".

There is a nice code example:

Lazy<Task<Person>> person = new Lazy<Task<Person>>(
     async () =>
     {
         using (var cmd = new SqlCommand(cmdText, conn))
         using (var reader = await cmd.ExecuteReaderAsync())
         {
             // some code...
         }
     });

async Task<Person> FetchPerson()
{
    return await person.Value;
}

The author said:

Because the lambda expression is asynchronous, it can be executed on any thread that calls Value, and the expression will run within the context.

As i understand it, the Thread come to FetchPerson and is stuck in Lamda execution. Is that realy bad? What consequences?

As a solution, the author suggest to create a Task:

Lazy<Task<Person>> person = new Lazy<Task<Person>>(
      () => Task.Run(
        async () =>
        {
            using (var cmd = new SqlCommand(cmdText, conn))
            using (var reader = await cmd.ExecuteReaderAsync())
            {
                // some code...
            }
        }));

Is that really correct? This is an IO operation, but we steal CPU thread from Threadpool.

Because the lambda expression is asynchronous, it can be executed on any thread that calls Value, and the expression will run within the context.

The lambda can be run from any thread (unless you're careful about what types of threads you let access the value of the Lazy), and as such it will be run in the context of that thread. That's not because it's asynchronous, it would be true even if it was synchronous that it'd run in the context of whatever thread happens to call it.

As i understand it, the Thread come to FetchPerson and is stuck in Lamda execution.

The lambda is asynchronous, as such it will (if implemented properly) return almost immediately. That's what it means to be asynchronous, as such it won't block the calling thread.

Is that realy bad? What consequences?

If you implement your asynchronous method incorrectly, and have it doing long running synchronous work, then yeah, you're blocking that thread/context. If you don't, you aren't.

Additionally, by default all of the continuations in your asynchronous methods will run in the original context (if it has a SynchonrizationContext at all). In your case your code almost certainly doesn't rely on re-using that context (because you don't know what contexts your caller might have, I can't imagine you wrote the rest of the code to use it). Given that, you can call .ConfigureAwait(false) on anything that you await, so that you don't use the current context for those continuations. This is simply a minor performance improvement in order to not waste time scheduling work on the original context, waiting for anything else that needs it, or making anything else wait on this code when unnecessarily.

As a solution, the author suggest to create a Task: [...] Is that really correct?

It won't break anything. It will schedule the work to run in a thread pool thread, rather than the original context. That's going to have some extra overhead to start with. You can accomplish approximately the same thing with lower overhead by simply adding ConfigureAwait(false) to everything that you await.

This is an IO operation, but we steal CPU thread from Threadpool.

That snippet will start the IO operation on a thread pool thread. Because the method is still asynchronous it will return it to the pool as soon as it starts it, and get a new thread from the pool to start running again after each await. The latter is likely appropriate for this situation, but moving the code to start the initial asynchronous operation to a thread pool thread is just adding overhead for no real value (because it's such a short operation you'll spend more effort scheduling it on a thread pool thread than just running it).

Maximizing Throughput, I created four tests: Signalled Counters using Delegate Task; Signalled Counters using Multi-threading; Time Delayed Counters using Delegate  The cause of the additional overhead in time delay task is Task.Delay, which uses a doubly linked list to provide O(1)inserts and deletes at the expense of O(n)scans for triggering timers. In contrast, time delay thread uses a priority queue, which provides O(log n)inserts and deletes and importantly, O(1)access to the next timer.

It is true that the first thread to access Value will execute the lambda. Lazy is unaware of async and tasks entirely. It will just run that delegate.

The delegate in this example will run on the calling thread until an await is hit. Then it will return a Task, that Task goes into the lazy and the lazy is entirely done at this point.

The rest of that task will run like any other task. It will respect the SynchronizationContext and TaskScheduler that were set when the await happened (this is part of await behavior). This can indeed lead to that code running in an unexpected context such as the UI thread.

Task.Run is a way to avoid that. It moves the code to the thread pool giving it a certain context. The overhead consists in queuing that work to the pool. The pool task will end at the fist await. So this is not async-over-sync. No blocking is introduced. The only change is on what thread CPU-based work happens (now deterministically on the thread pool).

It is fine to do this. It is an easy, maintainable, low-risk solution to a practical problem. There are different opinions about whether it is worth to do this or not. The overhead, in all likelyhood, will not matter. I personally am very sympathetic to this kind of code.

If you are sure that all callers of Value run in a suitable context then you don't need this. But if you make a mistake it's a serious bug. So you can argue that it is better to defensively insert Task.Run. Be pragmatic and do what works.

Also note, that Task.Run is async aware (so to speak). The task that it returns will essentially unwrap the inner task (unlike Task.Factory.StartNew). So it'S safe to nest tasks like it is done here.

How Does Task in C# Affect Performance?, So I'm creating 484 tasks, and saying "run these when you can". That can't be good for performance, right? It runs faster than when it was single-  The forall loop creates n parallel tasks to execute the loop body. These n tasks terminate and join at the endoftheforallloop. Thesetaskcreationsandterminationsare repeateduntiltheterminationofthewhileloop. Sincethewhile loop may be repeated a large number of times, such a program in-curs a large overhead in terms of task creation and termination. A

I totally don't understand why Terrell R. suggests to use Task.Run. It has no added value whatsoever. In both cases the lambda will get scheduled to the Thread pool. Since it contains IO operations, the worker thread from the thread pool will get released after the IO call; when the IO call completes, the next statement will continue on an arbitrary thread from the thread pool.

Seems that the author writes:

the expression will run within the context

Yes, the execution of the IO calls will start within the context of the caller, but will finish in an arbitrary context, unless you call .ConfigureAwait.

Creating Overhead Task, Creating Overhead Task. Overhead costs are usually incurred by the general contractor, and arise from the costs of supporting the construction activities. the total amount of overhead incurred by a program due to exces-sive task creation and termination. We introduce a transformation framework to optimize task-parallel programs with finish, forall and next statements. Our approach includes elimination of redun-dant task creation and termination operations as well as strength

The performance characteristics of async methods in C#, But to do that the compiler creates a state machine instance, pass it around to an async method builder, that calls task awaiter etc. Obviously, all  In this paper, we address the problem of reducing the total amount of overhead incurred by a program due to exces-sive task creation and termination. We introduce a transformation framework to optimize task-parallel programs with finish, forall and next statements.

Reducing Task Overhead, Task overhead is the time spent creating a task and getting it assigned to a thread, and also the time spent stopping or pausing the thread when the task is  I have an "overhead" task for tracking work related to administration of a project. As such, the project requires about 5 hours of administrative overhead work per week of its existence. How can I define such a task that will always be equivalent to 5 hours of work per week for the life of the project?

Applications, Tools and Techniques on the Road to Exascale Computing, In order to solve the overhead problem, specifically the task creation overhead, lazy task creation is well known as the most efficient technique. In this paper, the​  Overloads of the TaskFactory.StartNew method let you specify parameters to pass to the task creation options and a task scheduler. The following example uses the TaskFactory.StartNew method to start a task. It is functionally equivalent to the code in the previous example.

Comments
  • Not sure why author suggests to use Task.Run, but when using async/await we never steal CPU thread. When we call Task.Run then sure it can be run on a thread pool, but only the first part of a method before first async. Once it hits async it returns back to the thread pool. Also, once the await finishes it returns back to the original thread context, so from the point of view of FetchPerson it doesn't really matter on which thread the code executed.
  • Why would the lambda would "get scheduled to the Thread pool" if you don't call Task.Run? There is no additional thread involved here (without the use of Task.Run).
  • The lambda will start on the current thread, but up to the first await. After that it is up to the scheduler.
  • Yes. So the thread pool is not involved assuming the current thread has a SynchronizationContext.