I am playing a little bit with ConfigureAwait because I would like to understand how it works.
Therfore, I wrote the following small console application (actually running in LINQPad):
void Main()
{
// Use dispatcher to execute the code on STA thread
Dispatcher.CurrentDispatcher.Invoke(() => Run());
}
private async static Task Run()
{
var continueOnCapturedContext1 = true;
var continueOnCapturedContext2 = true;
PrintThreadID();
await Task.Run(() => PrintThreadID()).ConfigureAwait(continueOnCapturedContext1);
PrintThreadID();
await Task.Run(() => PrintThreadID()).ConfigureAwait(continueOnCapturedContext2);
PrintThreadID();
}
private static void PrintThreadID()
{
Console.Write(Thread.CurrentThread.ManagedThreadId.ToString("00") + "\t");
}
And I got the following output:
A) true/true
var continueOnCapturedContext1 = true;
var continueOnCapturedContext2 = true;
1) 11 19 11 07 11
2) 11 09 11 12 11
3) 11 06 11 06 11
Expected: dispatcher thread (11) was captured and awaitable tasks were executed on different or same threadpool threads.
B) false/false
var continueOnCapturedContext1 = false;
var continueOnCapturedContext2 = false;
1) 11 23 23 22 22
2) 11 19 19 19 19
3) 11 10 10 10 10
Also expected: SynchronizationContext was not captured, so subsequent awaitable and non-awaitable code were run on threadpool thread (usually the same).
C) false/true
var continueOnCapturedContext1 = false;
var continueOnCapturedContext2 = true;
1) 11 14 14 06 06
2) 11 20 20 20 20
3) 11 17 17 08 08
The result of output 1 and 3 is strange. The 2. awaitbale task was executed with option "continue on captured context" so I would expect that it runs on the same thread as the code that called it.
It seems, that ConfigureAwait(true/false) has no effect on subsequent awaitable calls if it was alreay called before, right?
The 2. awaitbale task was executed with option "continue on captured context" so I would expect that it runs on the same thread as the code that called it.
That assumes that "context == thread", but it doesn't. The synchronization context that uses the thread-pool will resume on any thread in the thread-pool. Now if you don't capture the synchronization context, you'll still end up on "a thread in the thread pool".
So yes, if you're already on a thread-pool thread, it won't matter whether or not you capture the synchronization context... the continuation will still end up on a thread pool thread. But it's worth pointing out that it would be entirely reasonable to have a different synchronization context with multiple threads, and capturing the synchronization context would return to one of those threads, but not necessarily the same one.
It seems, that ConfigureAwait(true/false) has no effect on subsequent awaitable calls if it was already called before, right?
Not quite. That will be the case if the task needs to use the continuation. If the first task you call ConfigureAwait on has already completed, the code will continue to execute synchronously - so at that point, the second ConfigureAwait is important.
Example:
using System;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;
class Test
{
static void Main()
{
var form = new Form();
form.Load += async (sender, args) =>
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.FromResult(10).ConfigureAwait(false);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1000).ConfigureAwait(false);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
};
Application.Run(form);
}
}
Sample output:
1
1
5
So the second Console.WriteLine showed that the code was still running on the same thread despite the ConfigureAwait(false), because Task.FromResult returns an already-completed task.
Related
I've created two lists of tasks, and I'd like to start processing both lists concurrently.
Although the total number of tasks is relatively small (perhaps less that 10 in total), processing time for each task varies, and I can start two tasks simultaneously as they target a different API.
To differentiate the API I've created two lists, and have populated each task list based on the API that will be utilized.
Within each list of task, I'd like their tasks processed sequentially; however, the processing order in list is not important.
At any given instance, two tasks could be running concurrently as long as the two tasks are not members of same task list.
My first thought was to build along these lines:
var lgoTasks = new List<Task>();
foreach (var x in lgo)
{
var t = sClient.SubmitToMarket(x.Id);
lgoTasks.Add(t);
}
var opApiTasks = new List<Task>();
foreach (var x in opApi)
{
var t = sClient.SubmitToMarket(x.Id);
opApiTasks.Add(t);
}
await Task.WhenAll(lgoTasks, opApiTasks);
But it fails for two (or more?) reason
WhenAll() won't accept two (or more) Task Arrays
Even if WhenAll() accepted two Task Arrays, the processing isn't sequential
Next I tried this approach:
var lgoCount = lgo.Count();
var opApiCount = opApi.Count();
var maxCounter = lgoCount > opApiCount ? lgoCount : opApiCount;
try
{
for (int i = 0; i < maxCounter; i++)
{
if (lgoCount - 1 >= i && opApiCount - 1 >= i)
{
var x = sClient.SubmitToMarket(lgo[i].Id);
var y = sClient.SubmitToMarket(opApi[i].Id);
await Task.WhenAll(x, y);
}
else if (lgoCount - 1 >= i)
{
await sClient.SubmitToMarket(Convert.ToInt32(lgo[i].Id));
}
else if (opApiCount - 1 >= i)
{
await sClient.SubmitToMarket(Convert.ToInt32(opApi[i].Id));
}
}
And although it works somewhat, WHENALL() creates a blocker until both its tasks are completed.
How do I concurrently start each list of tasks, making sure just one task from each list is running, and without creating blockers (like I did) using WHENALL()? Before my method returns control to the caller, all tasks in each task list are required to finish.
Thanks kindly for considering my question, and I look forward to your comments.
Solution
You have: two list of tasks (which all btw. are started, please see below)
You need: two tasks each of which executes its 'children' sequentially.
Here's a toy example:
Console.WriteLine("Main start");
await Task.WhenAll(WorkAsync("A"), WorkAsync("B"));
Console.WriteLine("Main done.");
async Task WorkAsync(string type)
{
for(int i = 0; i < 3; i++ )
await WaitAndPrintAsync(type+i);
}
async Task WaitAndPrintAsync(string s)
{
Console.WriteLine($"{s} start");
await Task.Delay(TimeSpan.FromMilliseconds(100));
Console.WriteLine($"{s} end");
}
This results in
Main start
A0 start
B0 start
B0 end
A0 end
A1 start
B1 start
A1 end
B1 end
A2 start
B2 start
B2 end
A2 end
Main done.
Tasks are already started
When you're doing
var lgoTasks = new List<Task>();
foreach (var x in lgo)
{
var t = sClient.SubmitToMarket(x.Id);
lgoTasks.Add(t);
}
you are starting the tasks and just not waiting for their finish.
Here's a simple example to prove this.
Console.WriteLine("Main start");
var lgoTasks = new List<Task>();
for (int i = 0; i < 3; i++)
{
Task t = WaitAndPrintAsync("A" + i);
lgoTasks.Add(t);
}
var opApiTasks = new List<Task>();
for (int i = 0; i < 3; i++)
{
Task t = WaitAndPrintAsync("B" + i);
opApiTasks.Add(t);
}
Console.WriteLine("Before WhenAll");
var allTasks = lgoTasks.Concat(opApiTasks);
await Task.WhenAll(allTasks);
Console.WriteLine("Main done.");
async Task WaitAndPrintAsync(string s)
{
Console.WriteLine($"{s} start");
await Task.Delay(TimeSpan.FromMilliseconds(100));
Console.WriteLine($"{s} end");
}
This prints:
Main start
A0 start
A1 start
A2 start
B0 start
B1 start
B2 start
Before WhenAll
B0 end
B2 end
B1 end
A1 end
A2 end
A0 end
Main done.
Notes:
All tasks are started before WhenAll is called.
Since and opApiTasks are list of independent tasks I was able to just concatenate both lists and use Task WhenAll(IEnumerable<Task> tasks); to wait for them to finish.
This is intended as an improved version of BitLauncher's answer. Instead of creating two cold tasks with the Task constructor and starting them with the Start method, we define two asynchronous methods and we invoke them sequentially. This way we get two tasks that have already started.
async Task Run1()
{
foreach (var x in lgo)
{
await sClient.SubmitToMarket(x.Id);
}
}
async Task Run2()
{
foreach (var x in opApi)
{
await sClient.SubmitToMarket(x.Id);
}
}
Task task1 = Run1();
Task task2 = Run2();
await Task.WhenAll(task1, task2);
It's a simple but not perfect solution. The two tasks (task1 and Task2) are not coordinated, and so a failure in one task will not cause the promptly termination of the other. In case this is a problem, you could consider fixing it by adding a CancellationTokenSource in the mix, observing it in each iteration in both tasks, and canceling it in the catch block of both tasks.
There is also some code duplication, that is going to be increased if you follow the CancellationTokenSource advice. I would probably improve it quite a lot if it was for my own project, but I think that this simple form is quite good for demonstration purposes.
Update: In case the SubmitToMarket is a method with asynchronous contract but synchronous implementation, then you can ensure¹ that both loops will start working at the same time by invoking them on the ThreadPool. This can be done by wrapping them in a Task.Run like this:
Task task1 = Task.Run(() => Run1());
Task task2 = Task.Run(() => Run2());
¹ Provided that the ThreadPool is not saturated.
Does SubmitToMarket start already a new task and return it? Then e. g. all lgoTasks will run concurrently!
But AFAIK your description, my solution would be about (no warranty for syntax errors):
var lgoSequenceTask = new Task(async () =>
{
foreach (var x in lgo) // execute in sequence (in different tasks)
{
await sClient.SubmitToMarket(x.Id);
}
}
);
var opApiSequenceTask = new Task(async () =>
{
foreach (var x in opApi) // execute in sequence (in different tasks)
{
await sClient.SubmitToMarket(x.Id);
}
}
);
var bothTasks = new List<Task>() { lgoSequenceTask, opApiSequenceTask};
foreach (var task in bothTasks) // start both sequences concurrently
{
task.Start();
}
await Task.WhenAll(bothTasks);
My understandig is that you want to have such a timing as an example:
a b
lgo: <-L1-><----L2---><-L3-><---L4-->|
opApi: <--O1--><-O2-><----O3---> |
times:
a: both tasks start in loop,
b: Task.WhenAll finished at |
Updated
The commenters wrote valuable input. I added a running solution in my async await sandbox project.
A note: if SubmitToMarket does not/never change the thread inside (can happen) then some solutions won't run really concurrently, but fully sequentially, therefore I added the variable doesSubmitChangeThread and lgoBgTask/opBgTask.
If SubmitToMarket always/mostly returns in another thread than it was started, my code parts with lgoBgTask/opBgTask are not needed (but doesSubmitChangeThread would have to be set true).
and ran it twice:
private bool doesSubmitChangeThread = false;
private async void StackOverflowQuestion71505300Async(bool awaitChecked, bool runInOwnThreadChecked)
{
var lgo = new string[] { "A1", "A2", "A3" };
var opApi = new String[] { "B1", "B2", "B3" };
Log("Starting tasks");
// intentionally start two tasks
var lgoBgTask = Task.Run<Task>(() => RunListInSequenceReturningTask(lgo));
var opBgTask = Task.Run<Task>(() => RunListInSequenceReturningTask(opApi));
Log("Waiting for results"); // results, that are awaitable
var lgoSequenceTask = lgoBgTask.Result;
var opApiSequenceTask = opBgTask.Result;
Log("Awaiting for all tasks...");
await Task.WhenAll(lgoSequenceTask, opApiSequenceTask);
Log("All tasks are done.");
}
private Task RunListInSequenceReturningTask(string[] list)
{
try
{
return RunListInSequenceAsync(list);
}
catch (Exception exc)
{
return Task.FromException(exc);
}
}
private async Task RunListInSequenceAsync(string[] list)
{
foreach (var x in list) // execute in sequence (changing optionally threads)
{
await sClient_SubmitToMarket(x);
}
}
private async Task sClient_SubmitToMarket(string x)
{
if (doesSubmitChangeThread)
{
await Task.Run(() => Submit(x));
}
else
{
Submit(x);
await Task.CompletedTask;
}
}
private void Submit(string x)
{
Log($"{x} start");
Thread.Sleep(100);
Log($"{x} end");
}
Depending on the variable there are different outputs:
doesSubmitChangeThread = false
Example 'Stackoverflow 2 lists concurrently' starting:
001 2022-03-20T15:02:30.8741675+01:00 Thread 1: Run method in UI thread
002 2022-03-20T15:02:30.8811230+01:00 Thread 1: Starting tasks
003 2022-03-20T15:02:30.8913657+01:00 Thread 1: Waiting for results
004 2022-03-20T15:02:30.9293240+01:00 Thread 5: B1 start
005 2022-03-20T15:02:30.9293240+01:00 Thread 3: A1 start
006 2022-03-20T15:02:31.0322484+01:00 Thread 3: A1 end
007 2022-03-20T15:02:31.0322484+01:00 Thread 5: B1 end
008 2022-03-20T15:02:31.0326192+01:00 Thread 3: A2 start
009 2022-03-20T15:02:31.0326192+01:00 Thread 5: B2 start
011 2022-03-20T15:02:31.1417688+01:00 Thread 3: A2 end
010 2022-03-20T15:02:31.1417688+01:00 Thread 5: B2 end
012 2022-03-20T15:02:31.1417688+01:00 Thread 3: A3 start
013 2022-03-20T15:02:31.1421069+01:00 Thread 5: B3 start
015 2022-03-20T15:02:31.2505188+01:00 Thread 3: A3 end
014 2022-03-20T15:02:31.2505188+01:00 Thread 5: B3 end
016 2022-03-20T15:02:31.2544658+01:00 Thread 1: Awaiting for all tasks...
017 2022-03-20T15:02:31.2564643+01:00 Thread 1: All tasks are done.
doesSubmitChangeThread = true
Example 'Stackoverflow 2 lists concurrently' starting:
001 2022-03-20T15:05:02.5879830+01:00 Thread 1: Run method in UI thread
002 2022-03-20T15:05:02.5929698+01:00 Thread 1: Starting tasks
003 2022-03-20T15:05:02.6019152+01:00 Thread 1: Waiting for results
004 2022-03-20T15:05:02.6480793+01:00 Thread 5: A1 start
005 2022-03-20T15:05:02.6480793+01:00 Thread 6: B1 start
006 2022-03-20T15:05:02.6593799+01:00 Thread 1: Awaiting for all tasks...
007 2022-03-20T15:05:02.7651641+01:00 Thread 6: B1 end
008 2022-03-20T15:05:02.7651641+01:00 Thread 5: A1 end
009 2022-03-20T15:05:02.7653251+01:00 Thread 3: B2 start
010 2022-03-20T15:05:02.7681511+01:00 Thread 6: A2 start
012 2022-03-20T15:05:02.8757320+01:00 Thread 3: B2 end
011 2022-03-20T15:05:02.8757320+01:00 Thread 6: A2 end
014 2022-03-20T15:05:02.8767286+01:00 Thread 5: A3 start
013 2022-03-20T15:05:02.8760494+01:00 Thread 4: B3 start
016 2022-03-20T15:05:02.9857143+01:00 Thread 5: A3 end
015 2022-03-20T15:05:02.9857143+01:00 Thread 4: B3 end
017 2022-03-20T15:05:02.9897134+01:00 Thread 1: All tasks are done.
What are your opinions about the update?
You should use Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive and add using System.Reactive.Linq; - then you can do this:
var query1 = lgo.ToObservable().SelectMany(x => Observable.FromAsync(() => sClient.SubmitToMarket(x.Id)));
var query2 = opApi.ToObservable().SelectMany(x => Observable.FromAsync(() => sClient.SubmitToMarket(x.Id)));
await query1.Merge(query2);
Alternatively, if you don't want the beauty of Rx, then just do this for your WhenAll:
await Task.WhenAll(lgoTasks.Concat(opApiTasks));
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.
I have one button and when I click on it, I am creating one thread and after 10 seconds I am aborting it. When I click on the button again it creates new thread, but the issue is when I use AWAIT, the thread is not aborted. Application is showing old thread's value with newly created thread. But when I am not using AWAIT, it's working fine. Below is just example of my Code.
Logic with AWAIT (you can see in the below image that old thread is also running)
try
{
var _Thread1 = new Thread(async () =>
{
int i = new Random().Next(10, 99);
while (true)
{
this.BeginInvoke(new Action(() =>
{
listBox1.Items.Add($"{i}");
}));
await Task.Delay(3000);
}
});
_Thread1.Start();
Thread.Sleep(10000);
_Thread1.Abort();
}
catch (Exception ex)
{
}
OUTPUT
48
48
48
48
48
48
83
48
83
48
83
48
83
Logic Without Await (below logic works file).
try
{
var _Thread1 = new Thread(async () =>
{
int i = new Random().Next(10, 99);
while (true)
{
this.BeginInvoke(new Action(() =>
{
listBox1.Items.Add($"{i}");
}));
Thread.Sleep(3000);
//await Task.Delay(3000);
}
});
_Thread1.Start();
Thread.Sleep(10000);
_Thread1.Abort();
}
catch (Exception ex)
{
}
OUTPUT.
98
98
98
98
79
79
79
79
I want to abort the thread when it's using AWAIT also.
I can do with CancellationToken/Task, but is there any other way? I want to know why thread is not aborted when AWAIT is used.
Thank you in advance. :)
I suppose the reason for the behavior you are observing is that the continuation for the asynchronous operation is scheduled on a ThreadPool thread. So, even though you have aborted the thread your code was running on initially, the continuation was scheduled and then executed on a different thread.
As it was already mentioned in the comments, you should never combine explicit Thread usage with async / await. Moreover, for all new code you are writing async / await is the preferred approach.
Here is the link to an answer by Stephen Cleary which clarifies this a bit more.
I have an app (http web load test app) that need new Thread() , and the HttpClient only have async method, so how do I run the action synchronous
ps: I tried use full Task but the thread number it use is low (30 thread only),
so I want to try the Thread to see if it can be much faster.
Will the .GetAwaiter().GetResult() cost 2 thread (100 thread became 200 thread) ?
previous I use
for(var i = 0; i< 200;i++)
{
Task.Run(async ()=>
{
while(thereStillHaveRequestToMake)
{
await httpclient.SendAsync() // some thing like this
}
});
}
// the prolem is there are only 30-40 Thread in use (From TaskManager)
So I want to switch to use Thread directly
for(var i = 0; i< 200;i++)
{
new Thread(()=>
{
while(thereStillHaveRequestToMake)
{
httpclient.SendAsync().GetAwaiter.GetResult()
}
});
}
I have an app (http web load test app) that need new Thread()
Why?
HttpClient only have async method, so how do I run the action synchronously
Why.
Or How to call asynchronous method from synchronous method in C#?.
I tried use full Task but the thread number it use is low (30 thread only),
A task is not a thread. We can easily test this by running methods on the thread pool. First we set the ThreadPool to only allow a single thread.
class Program
{
private const int MaxThreads = 1;
static void Main(string[] args)
{
ThreadPool.SetMinThreads(MaxThreads, 1);
Console.WriteLine(ThreadPool.SetMaxThreads(MaxThreads, 1));
Task.Run(() => SomeMethod(new StateInfo { Order = 0, WaitFor = 3000 }));
Task.Run(() => SomeMethod(new StateInfo { Order = 1, WaitFor = 3000 }));
Task.Run(() => SomeMethod(new StateInfo { Order = 2, WaitFor = 3000 }));
Console.WriteLine("Main thread does some work, then sleeps.");
Thread.Sleep(5000);
Console.WriteLine("Main thread exits.");
}
static void SomeMethod(Object stateInfo)
{
var si = (StateInfo)stateInfo;
Console.WriteLine($"Hello from the thread pool. {si.Order}");
Thread.Sleep(si.WaitFor);
}
public class StateInfo
{
public int Order { get; set; }
public int WaitFor { get; set; }
}
}
Output
True
Main thread does some work, then sleeps.
Hello from the thread pool. 1
Hello from the thread pool. 2
Main thread exits.
Since we have 1 thread and we've told the first two methods to wait a total of 6 seconds, but the main thread exits after 5 seconds, we never get a message from the 3rd method. We can easily test this by changing MaxThreads = 2 which yields something like the following (we get 3 results, but not necessarily in order):
True
Main thread does some work, then sleeps.
Hello from the thread pool. 1
Hello from the thread pool. 2
Hello from the thread pool. 3
Main thread exits.
Now that we've guaranteed we're using a single thread, lets see how many requests we can do simultaneously synchronously.
static void SomeMethod(Object stateInfo)
{
var si = (StateInfo)stateInfo;
Console.WriteLine($"Hello from the thread pool. {si.Order}");
httpClient.GetStringAsync($"https://www.google.com");
Console.WriteLine($"Hello from the thread pool. {si.Order} finished");
}
Since we aren't async/await the request, it runs synchronously so the output is predictably:
True
Main thread does some work, then sleeps.
Hello from the thread pool. 1
Hello from the thread pool. 1 finished
Hello from the thread pool. 2
Hello from the thread pool. 2 finished
Hello from the thread pool. 3
Hello from the thread pool. 3 finished
Main thread exits.
That doesn't really load test anything because synchronous calls wait until the previous one finishes. In order to load test we want many concurrent calls. This is easily done with a single thread using async await.
Update the method:
static async Task SomeMethod(Object stateInfo)
{
var si = (StateInfo)stateInfo;
Console.WriteLine($"Hello from the thread pool. {si.Order}");
await httpClient.GetStringAsync($"https://www.google.com");
Console.WriteLine($"Hello from the thread pool. {si.Order} finished");
}
Use linq to make a list of requests, and wait for all of them to finish.
static void Main(string[] args)
{
ThreadPool.SetMinThreads(MaxThreads, 1);
Console.WriteLine(ThreadPool.SetMaxThreads(MaxThreads, 1));
Console.WriteLine("Start Requests");
var requests = Enumerable.Range(0, 200)
.Select(async (x) => await Task.Run(() => SomeMethod2(new StateInfo { Order = x, WaitFor = 0 })))
.ToArray();
Console.WriteLine("Wait for them.");
Task.WaitAll(requests.ToArray());
Console.WriteLine("Main thread exits.");
Console.ReadKey();
}
Yields (I didn't want to put 400 lines of code here)
True
Start Requests
Wait for them.
Hello from the thread pool. 0
Hello from the thread pool. 1
Hello from the thread pool. 2
.... repeating to
Hello from the thread pool. 199
Hello from the thread pool. 178 finished
Hello from the thread pool. 5 finished
Hello from the thread pool. 3 finished
Hello from the thread pool. 15 finished
Hello from the thread pool. 26 finished
Hello from the thread pool. 4 finished
.... repeating until all 200 requests are finished
Main thread exits.
200 Http Requests on a single thread. Why do you need more threads?
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.