Using async/await for multiple tasks - c#

I'm using an API client that is completely asynchrounous, that is, each operation either returns Task or Task<T>, e.g:
static async Task DoSomething(int siteId, int postId, IBlogClient client)
{
await client.DeletePost(siteId, postId); // call API client
Console.WriteLine("Deleted post {0}.", siteId);
}
Using the C# 5 async/await operators, what is the correct/most efficient way to start multiple tasks and wait for them all to complete:
int[] ids = new[] { 1, 2, 3, 4, 5 };
Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait());
or:
int[] ids = new[] { 1, 2, 3, 4, 5 };
Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray());
Since the API client is using HttpClient internally, I would expect this to issue 5 HTTP requests immediately, writing to the console as each one completes.

int[] ids = new[] { 1, 2, 3, 4, 5 };
Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait());
Although you run the operations in parallel with the above code, this code blocks each thread that each operation runs on. For example, if the network call takes 2 seconds, each thread hangs for 2 seconds w/o doing anything but waiting.
int[] ids = new[] { 1, 2, 3, 4, 5 };
Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray());
On the other hand, the above code with WaitAll also blocks the threads and your threads won't be free to process any other work till the operation ends.
Recommended Approach
I would prefer WhenAll which will perform your operations asynchronously in Parallel.
public async Task DoWork() {
int[] ids = new[] { 1, 2, 3, 4, 5 };
await Task.WhenAll(ids.Select(i => DoSomething(1, i, blogClient)));
}
In fact, in the above case, you don't even need to await, you can just directly return from the method as you don't have any continuations:
public Task DoWork()
{
int[] ids = new[] { 1, 2, 3, 4, 5 };
return Task.WhenAll(ids.Select(i => DoSomething(1, i, blogClient)));
}
To back this up, here is a detailed blog post going through all the
alternatives and their advantages/disadvantages: How and Where Concurrent Asynchronous I/O with ASP.NET Web API

I was curious to see the results of the methods provided in the question as well as the accepted answer, so I put it to the test.
Here's the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncTest
{
class Program
{
class Worker
{
public int Id;
public int SleepTimeout;
public async Task DoWork(DateTime testStart)
{
var workerStart = DateTime.Now;
Console.WriteLine("Worker {0} started on thread {1}, beginning {2} seconds after test start.",
Id, Thread.CurrentThread.ManagedThreadId, (workerStart-testStart).TotalSeconds.ToString("F2"));
await Task.Run(() => Thread.Sleep(SleepTimeout));
var workerEnd = DateTime.Now;
Console.WriteLine("Worker {0} stopped; the worker took {1} seconds, and it finished {2} seconds after the test start.",
Id, (workerEnd-workerStart).TotalSeconds.ToString("F2"), (workerEnd-testStart).TotalSeconds.ToString("F2"));
}
}
static void Main(string[] args)
{
var workers = new List<Worker>
{
new Worker { Id = 1, SleepTimeout = 1000 },
new Worker { Id = 2, SleepTimeout = 2000 },
new Worker { Id = 3, SleepTimeout = 3000 },
new Worker { Id = 4, SleepTimeout = 4000 },
new Worker { Id = 5, SleepTimeout = 5000 },
};
var startTime = DateTime.Now;
Console.WriteLine("Starting test: Parallel.ForEach...");
PerformTest_ParallelForEach(workers, startTime);
var endTime = DateTime.Now;
Console.WriteLine("Test finished after {0} seconds.\n",
(endTime - startTime).TotalSeconds.ToString("F2"));
startTime = DateTime.Now;
Console.WriteLine("Starting test: Task.WaitAll...");
PerformTest_TaskWaitAll(workers, startTime);
endTime = DateTime.Now;
Console.WriteLine("Test finished after {0} seconds.\n",
(endTime - startTime).TotalSeconds.ToString("F2"));
startTime = DateTime.Now;
Console.WriteLine("Starting test: Task.WhenAll...");
var task = PerformTest_TaskWhenAll(workers, startTime);
task.Wait();
endTime = DateTime.Now;
Console.WriteLine("Test finished after {0} seconds.\n",
(endTime - startTime).TotalSeconds.ToString("F2"));
Console.ReadKey();
}
static void PerformTest_ParallelForEach(List<Worker> workers, DateTime testStart)
{
Parallel.ForEach(workers, worker => worker.DoWork(testStart).Wait());
}
static void PerformTest_TaskWaitAll(List<Worker> workers, DateTime testStart)
{
Task.WaitAll(workers.Select(worker => worker.DoWork(testStart)).ToArray());
}
static Task PerformTest_TaskWhenAll(List<Worker> workers, DateTime testStart)
{
return Task.WhenAll(workers.Select(worker => worker.DoWork(testStart)));
}
}
}
And the resulting output:
Starting test: Parallel.ForEach...
Worker 1 started on thread 1, beginning 0.21 seconds after test start.
Worker 4 started on thread 5, beginning 0.21 seconds after test start.
Worker 2 started on thread 3, beginning 0.21 seconds after test start.
Worker 5 started on thread 6, beginning 0.21 seconds after test start.
Worker 3 started on thread 4, beginning 0.21 seconds after test start.
Worker 1 stopped; the worker took 1.90 seconds, and it finished 2.11 seconds after the test start.
Worker 2 stopped; the worker took 3.89 seconds, and it finished 4.10 seconds after the test start.
Worker 3 stopped; the worker took 5.89 seconds, and it finished 6.10 seconds after the test start.
Worker 4 stopped; the worker took 5.90 seconds, and it finished 6.11 seconds after the test start.
Worker 5 stopped; the worker took 8.89 seconds, and it finished 9.10 seconds after the test start.
Test finished after 9.10 seconds.
Starting test: Task.WaitAll...
Worker 1 started on thread 1, beginning 0.01 seconds after test start.
Worker 2 started on thread 1, beginning 0.01 seconds after test start.
Worker 3 started on thread 1, beginning 0.01 seconds after test start.
Worker 4 started on thread 1, beginning 0.01 seconds after test start.
Worker 5 started on thread 1, beginning 0.01 seconds after test start.
Worker 1 stopped; the worker took 1.00 seconds, and it finished 1.01 seconds after the test start.
Worker 2 stopped; the worker took 2.00 seconds, and it finished 2.01 seconds after the test start.
Worker 3 stopped; the worker took 3.00 seconds, and it finished 3.01 seconds after the test start.
Worker 4 stopped; the worker took 4.00 seconds, and it finished 4.01 seconds after the test start.
Worker 5 stopped; the worker took 5.00 seconds, and it finished 5.01 seconds after the test start.
Test finished after 5.01 seconds.
Starting test: Task.WhenAll...
Worker 1 started on thread 1, beginning 0.00 seconds after test start.
Worker 2 started on thread 1, beginning 0.00 seconds after test start.
Worker 3 started on thread 1, beginning 0.00 seconds after test start.
Worker 4 started on thread 1, beginning 0.00 seconds after test start.
Worker 5 started on thread 1, beginning 0.00 seconds after test start.
Worker 1 stopped; the worker took 1.00 seconds, and it finished 1.00 seconds after the test start.
Worker 2 stopped; the worker took 2.00 seconds, and it finished 2.00 seconds after the test start.
Worker 3 stopped; the worker took 3.00 seconds, and it finished 3.00 seconds after the test start.
Worker 4 stopped; the worker took 4.00 seconds, and it finished 4.00 seconds after the test start.
Worker 5 stopped; the worker took 5.00 seconds, and it finished 5.00 seconds after the test start.
Test finished after 5.00 seconds.

You can use the Task.WhenAll function, to which you can pass any number of tasks. The Task.WhenAll returns a new task that will complete when all the tasks have completed. Be sure to wait asynchronously on Task.WhenAll, to avoid blocking your UI thread:
public async Task DoSomethingAsync() {
Task[] tasks = new Task[numTasks];
for (int i = 0; i < numTasks; i++)
{
tasks[i] = DoChildTaskAsync();
}
await Task.WhenAll(tasks);
// Code here will execute on UI thread
}

Since the API you're calling is async, the Parallel.ForEach version doesn't make much sense. You shouldnt use .Wait in the WaitAll version since that would lose the parallelism Another alternative if the caller is async is using Task.WhenAll after doing Select and ToArray to generate the array of tasks. A second alternative is using Rx 2.0

Parallel.ForEach requires a list of user-defined workers and a non-async Action to perform with each worker.
Task.WaitAll and Task.WhenAll require a List<Task>, which are by definition asynchronous.
I found RiaanDP's response very useful to understand the difference, but it needs a correction for Parallel.ForEach. Not enough reputation to respond to his comment, thus my own response.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncTest
{
class Program
{
class Worker
{
public int Id;
public int SleepTimeout;
public void DoWork(DateTime testStart)
{
var workerStart = DateTime.Now;
Console.WriteLine("Worker {0} started on thread {1}, beginning {2} seconds after test start.",
Id, Thread.CurrentThread.ManagedThreadId, (workerStart - testStart).TotalSeconds.ToString("F2"));
Thread.Sleep(SleepTimeout);
var workerEnd = DateTime.Now;
Console.WriteLine("Worker {0} stopped; the worker took {1} seconds, and it finished {2} seconds after the test start.",
Id, (workerEnd - workerStart).TotalSeconds.ToString("F2"), (workerEnd - testStart).TotalSeconds.ToString("F2"));
}
public async Task DoWorkAsync(DateTime testStart)
{
var workerStart = DateTime.Now;
Console.WriteLine("Worker {0} started on thread {1}, beginning {2} seconds after test start.",
Id, Thread.CurrentThread.ManagedThreadId, (workerStart - testStart).TotalSeconds.ToString("F2"));
await Task.Run(() => Thread.Sleep(SleepTimeout));
var workerEnd = DateTime.Now;
Console.WriteLine("Worker {0} stopped; the worker took {1} seconds, and it finished {2} seconds after the test start.",
Id, (workerEnd - workerStart).TotalSeconds.ToString("F2"), (workerEnd - testStart).TotalSeconds.ToString("F2"));
}
}
static void Main(string[] args)
{
var workers = new List<Worker>
{
new Worker { Id = 1, SleepTimeout = 1000 },
new Worker { Id = 2, SleepTimeout = 2000 },
new Worker { Id = 3, SleepTimeout = 3000 },
new Worker { Id = 4, SleepTimeout = 4000 },
new Worker { Id = 5, SleepTimeout = 5000 },
};
var startTime = DateTime.Now;
Console.WriteLine("Starting test: Parallel.ForEach...");
PerformTest_ParallelForEach(workers, startTime);
var endTime = DateTime.Now;
Console.WriteLine("Test finished after {0} seconds.\n",
(endTime - startTime).TotalSeconds.ToString("F2"));
startTime = DateTime.Now;
Console.WriteLine("Starting test: Task.WaitAll...");
PerformTest_TaskWaitAll(workers, startTime);
endTime = DateTime.Now;
Console.WriteLine("Test finished after {0} seconds.\n",
(endTime - startTime).TotalSeconds.ToString("F2"));
startTime = DateTime.Now;
Console.WriteLine("Starting test: Task.WhenAll...");
var task = PerformTest_TaskWhenAll(workers, startTime);
task.Wait();
endTime = DateTime.Now;
Console.WriteLine("Test finished after {0} seconds.\n",
(endTime - startTime).TotalSeconds.ToString("F2"));
Console.ReadKey();
}
static void PerformTest_ParallelForEach(List<Worker> workers, DateTime testStart)
{
Parallel.ForEach(workers, worker => worker.DoWork(testStart));
}
static void PerformTest_TaskWaitAll(List<Worker> workers, DateTime testStart)
{
Task.WaitAll(workers.Select(worker => worker.DoWorkAsync(testStart)).ToArray());
}
static Task PerformTest_TaskWhenAll(List<Worker> workers, DateTime testStart)
{
return Task.WhenAll(workers.Select(worker => worker.DoWorkAsync(testStart)));
}
}
}
The resulting output is below. Execution times are comparable. I ran this test while my computer was doing the weekly anti virus scan. Changing the order of the tests did change the execution times on them.
Starting test: Parallel.ForEach...
Worker 1 started on thread 9, beginning 0.02 seconds after test start.
Worker 2 started on thread 10, beginning 0.02 seconds after test start.
Worker 3 started on thread 11, beginning 0.02 seconds after test start.
Worker 4 started on thread 13, beginning 0.03 seconds after test start.
Worker 5 started on thread 14, beginning 0.03 seconds after test start.
Worker 1 stopped; the worker took 1.00 seconds, and it finished 1.02 seconds after the test start.
Worker 2 stopped; the worker took 2.00 seconds, and it finished 2.02 seconds after the test start.
Worker 3 stopped; the worker took 3.00 seconds, and it finished 3.03 seconds after the test start.
Worker 4 stopped; the worker took 4.00 seconds, and it finished 4.03 seconds after the test start.
Worker 5 stopped; the worker took 5.00 seconds, and it finished 5.03 seconds after the test start.
Test finished after 5.03 seconds.
Starting test: Task.WaitAll...
Worker 1 started on thread 9, beginning 0.00 seconds after test start.
Worker 2 started on thread 9, beginning 0.00 seconds after test start.
Worker 3 started on thread 9, beginning 0.00 seconds after test start.
Worker 4 started on thread 9, beginning 0.00 seconds after test start.
Worker 5 started on thread 9, beginning 0.01 seconds after test start.
Worker 1 stopped; the worker took 1.00 seconds, and it finished 1.01 seconds after the test start.
Worker 2 stopped; the worker took 2.00 seconds, and it finished 2.01 seconds after the test start.
Worker 3 stopped; the worker took 3.00 seconds, and it finished 3.01 seconds after the test start.
Worker 4 stopped; the worker took 4.00 seconds, and it finished 4.01 seconds after the test start.
Worker 5 stopped; the worker took 5.00 seconds, and it finished 5.01 seconds after the test start.
Test finished after 5.01 seconds.
Starting test: Task.WhenAll...
Worker 1 started on thread 9, beginning 0.00 seconds after test start.
Worker 2 started on thread 9, beginning 0.00 seconds after test start.
Worker 3 started on thread 9, beginning 0.00 seconds after test start.
Worker 4 started on thread 9, beginning 0.00 seconds after test start.
Worker 5 started on thread 9, beginning 0.00 seconds after test start.
Worker 1 stopped; the worker took 1.00 seconds, and it finished 1.00 seconds after the test start.
Worker 2 stopped; the worker took 2.00 seconds, and it finished 2.00 seconds after the test start.
Worker 3 stopped; the worker took 3.00 seconds, and it finished 3.00 seconds after the test start.
Worker 4 stopped; the worker took 4.00 seconds, and it finished 4.00 seconds after the test start.
Worker 5 stopped; the worker took 5.00 seconds, and it finished 5.01 seconds after the test start.
Test finished after 5.01 seconds.

All the answers are for running the same function.
The following code works for calling different functions. Just put your regular Task.Run() inside an array and call with Task.WhenAll():
await Task.WhenAll(new Task[] {
Task.Run(() => Func1(args)),
Task.Run(() => Func2(args))
});

The question is 10 years old and OP was asking about C# 5.
As of today, there is one more option: Parallel.ForEachAsync method that was introduced in .NET 6.
Here is an example based on the OP's code:
int[] ids = new[] { 1, 2, 3, 4, 5 };
await Parallel.ForEachAsync(ids, async (i,token) => await DoSomething(1, i, blogClient));
This is completely asynchronous and doesn't block any threads.
Additionally, it might be better than Task.WaitAll and Task.WhenAll approaches because they don't limit the number of threads running in parallel. So if you have a huge array it can eat up all your RAM. Parallel.ForEachAsync allows you to specify parallelism degree like so:
var options = new ParallelOptions { MaxDegreeOfParallelism = 4 };
await Parallel.ForEachAsync(ids, options, async (i,token) => await DoSomething(1, i, blogClient));
This way you have only 4 threads running in parallel.

I just want to add to all great answers above,
that if you write a library it's a good practice to use ConfigureAwait(false)
and get better performance, as said here.
So this snippet seems to be better:
public static async Task DoWork()
{
int[] ids = new[] { 1, 2, 3, 4, 5 };
await Task.WhenAll(ids.Select(i => DoSomething(1, i))).ConfigureAwait(false);
}
A full fiddle link here.

Related

Only a few threads executing at a time [duplicate]

This question already has answers here:
ThreadPool not starting new Thread instantly
(2 answers)
Closed 1 year ago.
I am working on a program where we are constantly starting new threads to go off and do a piece of work. We noticed that even though we might have started 10 threads only 3 or 4 were executing at a time. To test it out I made a basic example like this:
private void startThreads()
{
for (int i = 0; i < 100; i++)
{
//Task.Run(() => someThread());
//Thread t = new Thread(() => someThread());
//t.Start();
ThreadPool.QueueUserWorkItem(someThread);
}
}
private void someThread()
{
Thread.Sleep(1000);
}
Simple stuff right? Well, the code creates the 100 threads and they start to execute... but only 3 or 4 at a time. When they complete the next threads start to execute. I would have expected that almost all of them start execution at the same time. For 100 threads (each with a 1 second sleep time) it takes about 30 seconds for all of them to complete. I would have thought it would have taken far less time than this.
I have tried using Thread.Start, ThreadPool and Tasks, all give me the exact same result. If I use ThreadPool and check for the available number of threads each time a thread runs there are always >2000 available worker threads and 1000 available async threads.
I just used the above as a test for our code to try and find out what is going on. In practice, the code spawns threads all over the place. The program is running at less than 5% CPU usage but is getting really slow because the threads aren't executing quick enough.
Yes you may only have a few threads running at the same time. That how a ThreadPool works. It doesn't necessarily run all the threads at the same time. It would queue them up fast, but then leave it to the ThreadPool to handle when each thread runs.
If you want to ensure all 100 threads run simultaneously you can use:
ThreadPool.SetMinThreads(100, 100);
For example, see the code below, this is the result without the thread pool min size:
No MinThreads
internal void startThreads()
{
ThreadPool.GetMaxThreads(out int maxThread, out int completionPortThreads);
stopwatch.Start();
var result = Parallel.For(0, 20, (i) =>
{
ThreadPool.QueueUserWorkItem(someThread, i);
});
while (!result.IsCompleted) { }
Console.WriteLine("Queueing completed...");
}
private void someThread(Object stateInfo)
{
int threadNum = (int)stateInfo;
Console.WriteLine(threadNum + " started.");
Thread.Sleep(10);
Console.WriteLine(threadNum + " finnished.");
}
Result (No MinThreads)
9 started.
7 started.
11 started.
10 started.
1 finnished.
12 started.
9 finnished.
13 started.
2 finnished.
4 finnished.
15 started.
3 finnished.
8 finnished.
16 started.
10 finnished.
6 finnished.
19 started.
0 finnished.
14 started.
5 finnished.
7 finnished.
17 started.
18 started.
11 finnished.
With MinThreads
internal void startThreads()
{
ThreadPool.GetMaxThreads(out int maxThread, out int completionPortThreads);
ThreadPool.SetMinThreads(20, 20); // HERE <-------
stopwatch.Start();
var result = Parallel.For(0, 20, (i) =>
{
ThreadPool.QueueUserWorkItem(someThread, i);
});
while (!result.IsCompleted) { }
Console.WriteLine("Queueing completed...");
}
Results
...
7 started.
15 started.
9 started.
12 started.
17 started.
13 started.
16 started.
19 started.
18 started.
5 finnished.
3 finnished.
4 finnished.
6 finnished.
0 finnished.
14 finnished.
1 finnished.
10 finnished.
...
A nice clean devise.

QueueUserWorkItem weird behavior

I'm using ThreadPool.QueueUserWorkItem() to start some background tasks.
The ThreadPool concurrency behavior is weird. My CPU has 4 logical cores. I expect that there are only 4 running threads. However, the sample code shows different behavior.
in time 1/2/3, Why does mores threads are triggered?
here is sample code:
class Program
{
static DateTime s_startTime = new DateTime();
public static void Main()
{
// Queue the task.
s_startTime = DateTime.Now;
for (int i = 0; i < 1000; i++)
{
ThreadPool.QueueUserWorkItem(ThreadProc, i);
}
Console.WriteLine("Main thread does some work, then sleeps.");
Thread.Sleep(100 * 1000);
Console.WriteLine("Main thread exits.");
}
// This thread procedure performs the task.
static void ThreadProc(Object i)
{
DateTime thread_starttime = DateTime.Now;
int a = Convert.ToInt32(i);
double ss = (thread_starttime - s_startTime).TotalSeconds;
Console.WriteLine("time:" + ss + ", start " + a);
Thread.Sleep(10 * 1000);
DateTime e = DateTime.Now;
double ee = (e - s_startTime).TotalSeconds;
Console.WriteLine("time:" + ee + ", end " + a);
}
}
output
Main thread does some work, then sleeps.
time:0.0040027, start 0
time:0.0360007, start 3
time:0.0360007, start 1
time:0.0360007, start 2
time:1.0178537, start 4
time:2.0191713, start 5
time:3.019311, start 6
time:4.0194503, start 7
time:5.0195775, start 8
time:6.0195875, start 9
time:7.0219127, start 10
time:8.0214611, start 11
time:9.0181507, start 12
time:10.020686, end 0
time:10.020686, start 13
time:10.020686, start 14
time:10.038517, end 1
time:10.038517, start 15
time:10.038517, end 3
time:10.0403473, start 16
time:10.038517, end 2
time:10.0413736, start 17
time:11.0233302, end 4
time:11.0243333, start 18
time:11.0243333, start 19
More threads will be triggered because you aren’t keeping your cores busy.
Thread.Sleep allows the thread to yield. Since your workers are mostly sleeping and not CPU bound the scheduler is free to schedule more threads.

Async methods running on same thread with no time delay

This is what AsyncMethods class looks like:
public class AsyncMethods
{
public static async Task<double> GetdoubleAsync()
{
Console.WriteLine("Thread.CurrentThread.ManagedThreadId: " + Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1000);
return 80d;
}
public static async Task<string> GetStringAsync()
{
Console.WriteLine("Thread.CurrentThread.ManagedThreadId: " + Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1000);
return "async";
}
public static async Task<DateTime> GetDateTimeAsync()
{
Console.WriteLine("Thread.CurrentThread.ManagedThreadId: " + Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1000);
return DateTime.Now;
}
}
This what my main method looks like:
static void Main(string[] args)
{
while (Console.ReadLine() != "exit")
{
Console.WriteLine("Thread.CurrentThread.ManagedThreadId: " + Thread.CurrentThread.ManagedThreadId);
DateTime dt = DateTime.Now;
var res = GetStuffAsync().Result;
var ts = DateTime.Now - dt;
Console.WriteLine(res);
Console.WriteLine("Seconds taken: " + ts.Seconds + " milliseconds taken: " + ts.Milliseconds);
}
Console.ReadLine();
return;
}
static async Task<object> GetStuffAsync()
{
var doubleTask = AsyncMethods.GetdoubleAsync();
var StringTask = AsyncMethods.GetStringAsync();
var DateTimeTask = AsyncMethods.GetDateTimeAsync();
return new
{
_double = await doubleTask,
_String = await StringTask,
_DateTime = await DateTimeTask,
};
}
As it can be seen in each method i added a delay of 1 second. Here is the output:
Thread.CurrentThread.ManagedThreadId: 10
Thread.CurrentThread.ManagedThreadId: 10
Thread.CurrentThread.ManagedThreadId: 10
Thread.CurrentThread.ManagedThreadId: 10
{ _double = 80, _String = async, _DateTime = 2/15/2017 4:32:00 AM }
Seconds taken: 1 milliseconds taken: 40
Thread.CurrentThread.ManagedThreadId: 10
Thread.CurrentThread.ManagedThreadId: 10
Thread.CurrentThread.ManagedThreadId: 10
Thread.CurrentThread.ManagedThreadId: 10
{ _double = 80, _String = async, _DateTime = 2/15/2017 4:32:03 AM }
Seconds taken: 1 milliseconds taken: 16
Now i have 2 questions:
How come everything happened on a single thread?
Why was the Delay only 1 second when i waited 3 seconds?
First off: if you have two questions please ask two questions. Don't put two questions in one question.
How come everything happened on a single thread?
That's the wrong question to ask. The correct question is: why do you think anything should happen on a second thread?
Here, I'll give you a task: wait five minutes, and then check your email. While you're waiting, make a sandwich. Did you have to hire someone to either do the waiting or make the sandwich? Obviously not. Threads are workers. There's no need to hire a worker if the job can be done by one worker.
The whole point of await is to avoid going to extra threads if you don't need to. In this case you don't need to.
Why was the Delay only 1 second when i waited 3 seconds?
Compare these two workflows.
Wait five minutes; while you're waiting, make a sandwich
then check your email
then wait five minutes; while you're waiting, make a sandwich
then check your email
then wait five minutes; while you're waiting, make a sandwich
then check your email
If you execute that workflow, you'll wait a total of fifteen minutes.
The workflow you wrote was:
Wait five minutes
simultaneously, wait five minutes
simultaneously, wait five minutes
while you're waiting, make a sandwich
then check your email
You only wait five minutes with that workflow; all the delays happen at the same time.
Do you see how you wrote your program incorrectly now?
The key insight to understand here is that an await is a point in a program where the continuation of the await is delayed until after the awaited task completes.
If you don't put in an await, the program continues by itself without waiting. That's the meaning of await.
They all start on the same thread. When you call your three Async methods in sequence, they all execute synchronously up until the first await call. (After the await, they become state machines that pick up where they left off whenever they get scheduled. If you checked the thread ID after the await Task.Delay call, you would probably find that the continuations ran on different threads -- at least here in a console app.)
As for why it's only delaying 1 second... that's what you're telling it to do. You've got three async tasks, all running simultaneously, each delaying for one second. You're not saying "[a]wait until the first task is done before starting the second" -- in fact you're carefully doing the opposite, starting all three and then awaiting all three -- so they run in parallel.
Your Console.WriteLine() calls in GetdoubleAsync(), GetStringAsync(), and GetDateTimeAsync() happened in the calling thread because they happened before the first continuation.
Your await Task.Delay() calls yielded the thread back to the calling code.
When the task returned by Task.Delay() completed, the continuation on those Tasks returned their values and set their tasks as completed.
This allowed your 3 awaits (in sequential, synchronous order) in GetStuffAsync() to return. Each one had to wait 1 second before marked as completed, but they were yielding and happening at the same time.
I think you are looking for System.Threading.Tasks.Parallel to do things at the same time. Async...await is useful for yielding threads.
You're starting all your tasks at the same time so they're all going to run in parallel, not in sequence. That's why everything completes after 1000 milliseconds.
Additionally, async doesn't create new threads, it uses the current thread asynchronously. You can see this kind of behaviour in async javascript (which is a single threaded environment) or coroutines in Unity3D. They both allow async behaviour without threads.
So each of your tasks is being run on the same thread and completes in 1 second.

Thread not run immediately when using more than 4 BackgroundWorker

I use multiple BackgroundWorker control to run some task as multithreading. But I found that when using more than 4 BackgroundWoker, the one from 4th forward delay more than second to actually execute from when calling RunWorkerAsync.
Could help me how can I start all backgroundworker immediately?
class TaskLog
{
public int task_id;
public DateTime call_time;
public DateTime start_time;
public DateTime end_time;
}
BackgroundWorker[] bws = new BackgroundWorker[18];
int[] tasks = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Queue<TaskLog> queueTask;
TaskLog[] records;
int task_complete = 999;
private void button1_Click(object sender, EventArgs e)
{
if (task_complete < tasks.Length) return;
task_complete = 0;
records = tasks.Select(t => new TaskLog { task_id = t }).ToArray();
queueTask = new Queue<TaskLog>(records);
for (int i = 0; i < bws.Length && queueTask.Count > 0; ++i)
{
bws[i] = new BackgroundWorker();
bws[i].DoWork += new DoWorkEventHandler(download_vid_work);
bws[i].RunWorkerCompleted += new RunWorkerCompletedEventHandler(download_vid_complete);
var x = queueTask.Dequeue();
x.call_time = DateTime.Now;
bws[i].RunWorkerAsync(x);
//Debug.WriteLine("start " + x.task_id);
}
}
void download_vid_work(object sender, DoWorkEventArgs e)
{
var record = (TaskLog)e.Argument;
record.start_time = DateTime.Now;
//Debug.WriteLine("actually start " + record.task_id);
Thread.Sleep(10000); // 10s
e.Result = record;
}
void download_vid_complete(object sender, RunWorkerCompletedEventArgs e)
{
var record = (TaskLog)e.Result;
record.end_time = DateTime.Now;
//Debug.WriteLine("complete " + item.ToString());
++task_complete;
if (task_complete == tasks.Length)
{
Debug.WriteLine("all tasks are completed!");
foreach (var r in records)
{
Debug.WriteLine("task {0} delay time: {1}", r.task_id, (r.start_time - r.call_time).TotalMilliseconds.ToString("0,0"));
}
}
else if (queueTask.Count > 0)
{
var bw = (BackgroundWorker)sender;
var nextTask = queueTask.Dequeue();
bw.RunWorkerAsync(nextTask);
nextTask.call_time = DateTime.Now;
}
}
Here is log result after run:
all tasks are completed!
task 1 delay time: 22
task 2 delay time: 24
task 3 delay time: 24
task 4 delay time: 23
task 5 delay time: 1,005
task 6 delay time: 2,002
task 7 delay time: 3,003
task 8 delay time: 4,003
task 9 delay time: 5,004
task 10 delay time: 6,005
The ThreadPool class, which manages the thread pool threads used for BackgroundWorker (and other needs), does not maintain an infinite number of worker threads ready to run.
You can configure the actual number of idle threads (*), using the ThreadPool.SetMinThreads() method. As you can see in your case, when you initially start your program, there are four idle threads ready to accept work right away. The default number of idles threads depends on a variety of things related to the OS version and configuration.
Once there are more queued work items for the thread pool than there are threads to service them, the ThreadPool class does not create new threads right away. It waits for a short period of time (as you can see from your test, one second), on the assumption that it's possible one of the other tasks may finish soon and it will be able to reuse that thread rather than going to all the trouble of creating yet another thread (which incurs its own overhead and would even slow down the work of the threads already running).
In general, you should avoid overriding the default values for the thread pool, as they are generally set correctly given your OS version, hardware, etc. For example, it won't help to have more CPU-bound threads running than you have CPU cores on the machine. Letting the ThreadPool class decide when and how to run your worker threads is usually the best approach.
(*) The above is a bit of an over-simplification. In newer versions of .NET, the minimum number of threads may or may not actually exist at any given time. If work items are queued when there are fewer than the minimum number, ThreadPool will immediately create new threads as needed up to the minimum. Beyond that, it then shifts to its more elaborate creation and scheduling logic.

Parallel Library: does a delay on one degree of parallelism delay all of them?

I have a ConcurrentBag urls whose items are being processed in parallel (nothing is being written back to the collection):
urls.AsParallel<UrlInfo>().WithDegreeOfParallelism(17).ForAll( item =>
UrlInfo info = MakeSynchronousWebRequest(item);
(myProgress as IProgress<UrlInfo>).Report(info);
});
I have the timeout set to 30 seconds in the web request. When a url that is very slow to respond is encountered, all of the parallel processing grinds to a halt. Is this expected behavior, or should I be searching out some problem in my code?
Here's the progress :
myProgress = new Progress<UrlInfo>( info =>
{
Action action = () =>
{
Interlocked.Increment(ref itested);
if (info.status == UrlInfo.UrlStatusCode.dead)
{
Interlocked.Increment(ref idead);
this.BadUrls.Add(info);
}
dead.Content = idead.ToString();
tested.Content = itested.ToString();
};
try
{
Dispatcher.BeginInvoke(action);
}
catch (Exception ex)
{
}
});
It's the expected behavior. AsParallel doesn't return until all the operations are finished. Since you're making synchronous requests, you've got to wait until your slowest one is finished. However note that even if you've got one really slow task hogging up a thread, the scheduler continues to schedule new tasks as old ones finish on the remaining threads.
Here's a rather instructive example. It creates 101 tasks. The first task hogs one thread for 5000 ms, the 100 others churn on the remaining 20 threads for 1000 ms each. So it schedules 20 of those tasks and they run for one second each, going through that cycle 5 times to get through all 100 tasks, for a total of 5000 ms. However if you change the 101 to 102, that means you've got 101 tasks churning on the 20 threads, which will end up taking 6000 ms; that 101th task just didn't have a thread to churn on until the 5 sec mark. If you change the 101 to, say, 2, you note it still takes 5000 ms because you have to wait for the slow task to complete.
static void Main()
{
ThreadPool.SetMinThreads(21, 21);
var sw = new Stopwatch();
sw.Start();
Enumerable.Range(0, 101).AsParallel().WithDegreeOfParallelism(21).ForAll(i => Thread.Sleep(i==0?5000:1000));
Console.WriteLine(sw.ElapsedMilliseconds);
}

Categories