-1

I did a sample to simulate concurrency using the code below:

var threads = new Thread[200]; 
//starting threads logic
for (int i = 0; i < 200; i++)
         {
             threads[i].Start();
         }
         for (int i = 0; i < 200; i++)
         {
             threads[i].Join();
         } 

The code is supposed to insert thousands of records to the database and it seems to work well, as the threads finished at almost the same time.

But, when I use:

 var tasks = new List<Task<int>>();
        for (int i = 0; i < 200; i++)
        {
            tasks.Add(insert(i));
            //   await insert(i);
        }
        int[] result = await Task.WhenAll(tasks);

it takes a lot of time to finish the same logic.

Can someone explain to me what's the difference? I thought that Await should create threads.

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
  • 4
    No. await does NOT create Threads. http://blog.stephencleary.com/2012/02/async-and-await.html The word Async has nothing to do with Multithreading at all in this context. – Aron Dec 30 '15 at 06:59
  • Where in code do you *start* your tasks ? – Fabjan Dec 30 '15 at 14:02
  • Are you aware that starting a thread consumes over 1MB of memory? So 200 is well over 200MB. And that's without doing any processing. And starting threads is slow. It's far far better to use them from the thread-pool. – Enigmativity Jan 01 '16 at 00:40
  • thanks guys , really your replies are helpful – Ahmad Abu-Hamideh Jan 03 '16 at 06:39

2 Answers2

1

If you need to replicate your original Thread-based behaviour, you can use Task.Factory.StartNew(... , TaskCreationOptions.LongRunning) to schedule your work, and then block until the worker tasks complete via Task.WaitAll. I do not recommended this approach, but in terms of behaviour this will be very close to how your code was working previously.

A more in-depth analysis as to why may not getting the expected performance in your scenario is as follows:

Explanation, part 1 (async does not mean "on a different thread")

Methods marked with the async keyword do not magically run asynchronously. They are merely capable of combining awaitable operations (that may or may not run asynchronously themselves), into a single larger unit (generally Task or Task<T>).

If your insert method is async, it is still likely that it performs at least some of the work synchronously. This will definitely be the case with all of your code preceding the first await statement. This work will execute on the "main" thread (thread which calls insert) - and that will be your bottleneck or at least part thereof as the degree of parallelism for that section of your code will be 1 while you're calling insert in a tight loop, regardless of whether you await the resulting task.

To illustrate the above point, consider the following example:

void Test()
{
    Debug.Print($"Kicking off async chain (thread {Thread.CurrentThread.ManagedThreadId}) - this is the main thread");
    OuterTask().Wait(); // Do not block on Tasks - educational purposes only.
}

async Task OuterTask()
{
    Debug.Print($"OuterTask before await (thread {Thread.CurrentThread.ManagedThreadId})");
    await InnerTask().ConfigureAwait(false);
    Debug.Print($"OuterTask after await (thread {Thread.CurrentThread.ManagedThreadId})");
}

async Task InnerTask()
{
    Debug.Print($"InnerTask before await (thread {Thread.CurrentThread.ManagedThreadId})");
    await Task.Delay(10).ConfigureAwait(false);
    Debug.Print($"InnerTask after await (thread {Thread.CurrentThread.ManagedThreadId}) - we are now on the thread pool");
}

This produces the following output:

Kicking off async chain (thread 6) - this is the main thread
OuterTask before await (thread 6)
InnerTask before await (thread 6)
InnerTask after await (thread 8) - we are now on the thread pool
OuterTask after await (thread 8)

Note that the code before the first await inside Task1 and even Task2 still executes on the "main" thread. Our chain actually executes synchronously, on the same thread which kicked off the outer task, until we await the first truly async operation (in this case Task.Delay).

Additionally

If you are running in an environment where SynchronizationContext.Current is not null (i.e. Windows Forms, WPF) and you're not using ConfigureAwait(false) on the tasks awaited inside your insert method, then continuations scheduled by the async state machine after the first await statement will also likely execute on the "main" thread - although this is not guaranteed in certain environments (i.e. ASP.NET).

Explanation, part 2 (executing Tasks on the thread pool)

If, as part of your insert method, you are opting to start any Tasks manually, then you are most likely scheduling your work on the thread pool by using Task.Run or any other method of starting a new task that does not specify TaskCreationOptions.LongRunning. Once the thread pool gets saturated any newly started tasks will be queued, thus reducing the throughput of your parallel system.

Proof:

IEnumerable<Task> tasks = Enumerable
    .Range(0, 200)
    .Select(_ => Task.Run(() => Thread.Sleep(100))); // Using Thread.Sleep to simulate blocking calls.

await Task.WhenAll(tasks); // Completes in 2+ seconds.

Now with TaskCreationOptions.LongRunning:

IEnumerable<Task> tasks = Enumerable
    .Range(0, 200)
    .Select(_ => Task.Factory.StartNew(
        () => Thread.Sleep(100), TaskCreationOptions.LongRunning
    ));

await Task.WhenAll(tasks); // Completes in under 130 milliseconds.

It is generally not a good idea to spawn 200 threads (this will not scale well), but if massive parallelisation of blocking calls is an absolute requirement, the above snippet shows you one way to do it with TPL.

Kirill Shlenskiy
  • 9,367
  • 27
  • 39
0

In first example you created threads manually. In second you created tasks. Task - probably - are using thread pool, where limited count of threads exist. So, most task ae waiting in queue, while few of them are executing in parallel on available threads.

codefox
  • 120
  • 8
  • Tasks (Await/Async) [do not create or use theads](http://blog.stephencleary.com/2013/11/there-is-no-thread.html), it is only a very complex state machine. – Erik Philips Dec 31 '15 at 02:59
  • @Erik Of course, but tasks are executing using threads. TaskSheduker manage tasks, and use threads from thread pool to execute them. https://msdn.microsoft.com/en-us/library/dd997402%28v=vs.110%29.aspx – codefox Dec 31 '15 at 10:13
  • thanks guys , really your replies are helpful – Ahmad Abu-Hamideh Jan 03 '16 at 06:39