Parallel Tasks being handled as sequential
I'm trying to wrap my head around the whole Parallel Programming concept, mostly focusing on Tasks, so I've been trying this scenario where, say, up to 9 Parallel Tasks would perform their work for a random period of time:
/// <remarks>
/// A big thank you to the awesome community of StackOverflow for
/// their advice and guidance with this sample project
/// http://stackoverflow.com/questions/5195486/
/// </remarks>
RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
byte[] buffer = new byte[4];
random.GetBytes(buffer);
// Creates a random number of tasks (not less than 2 -- up to 9)
int iteration = new Random(BitConverter.ToInt32(buffer, 0)).Next(2,9);
Console.WriteLine("Creating " + iteration + " Parallel Tasks . . .");
Console.Write(Environment.NewLine);
Dictionary<int, string> items = new Dictionary<int, string>();
for (int i = 1; i < iteration + 1; i++) // cosmetic +1 to avoid "Task N° 0"
{
items.Add(i, "Task # " + i);
}
List<Task> tasks = new List<Task>();
// I guess we should use a Parallel.Foreach() here
foreach (var item in items)
{
// Creates a random interval to pause the thread at (up to 9 secs)
random.GetBytes(buffer);
int interval = new Random(BitConverter.ToInt32(buffer, 0)).Next(1000, 9000);
var temp = item;
var task = Task.Factory.StartNew(state =>
{
Console.WriteLine(String.Format(temp.Value + " will be completed in {0} miliseconds . . .", interval));
Thread.Sleep(interval);
return "The quick brown fox jumps over the lazy dog.";
}, temp.Value).ContinueWith(t => Console.WriteLine(String.Format("{0} returned: {1}", t.AsyncState, t.Result)));
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
But unfortunately they're being handled sequentially instead of in parallel.
I'd be really glad if you guys could help me out here -- maybe I should use a Parallel.ForEach instead of a regular one?
Again any 开发者_Go百科advice would be really appreciated.
EDIT Updated the code sample twice to reflect the contributions from the commenters.
You're calling task.Result
on each task in the loop... which means it's going to wait for the result of one task before creating the next one. Try this instead:
// Create all the tasks
foreach (var item in items)
{
// ... Create and start tasks as before
tasks.Add(task);
}
// Now wait for them all, printing the results
foreach (var task in tasks)
{
Console.WriteLine(task.AsyncState + " returned: " + task.Result);
}
Now this will block on the first task created immediately - so even if (say) the 5th one completes much earlier, you won't see any results until the 1st one has completed... but they will be executing in parallel.
Next you'll want to change your loop like this:
foreach (var item in items)
{
var itemCopy = item;
// Use itemCopy in here instead of item
}
as otherwise you're capturing the loop variable, which is considered harmful (part 1; part 2).
But yes, you probably should use Parallel.ForEach
instead. It's worth understanding why it was failing before though.
Calling Task.Result
blocks. Since you're doing that inside your foreach
over items you end up creating one task and then waiting for it to finish before moving to the next item.
try moving the call to Task.Result
outside that foreach
List<Task<string>> tasks = new List<Task<string>>();
foreach (var item in items)
{
random.GetBytes(buffer);
int interval = new Random(BitConverter.ToInt32(buffer, 0)).Next(1000, 9000);
var task = Task.Factory.StartNew(state =>
{
Console.WriteLine(String.Format(item.Value + " will be completed in {0} miliseconds . . .", interval));
Thread.Sleep(interval);
return "The quick brown fox jumps over the lazy dog.";
}, item.Value);
tasks.Add(task);
}
foreach (var task in tasks)
{
Console.WriteLine(task.AsyncState + " returned: " + task.Result);
}
EDIT
As asked for in the comments. Here is a version that would print the result as each task finishes by using ConinueWith
. The task list can now be a List<Task>
again as well. The WaitAll
call is still needed at the end to make sure the method doesn't return until each task is done, but each task will print it's result as it finishes.
RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
byte[] buffer = new byte[4];
random.GetBytes(buffer);
// Creates a random number of tasks (not less than 2 -- up to 9)
int iteration = new Random(BitConverter.ToInt32(buffer, 0)).Next(2, 9);
Console.WriteLine("Creating " + iteration + " Parallel Tasks . . .");
Dictionary<int, string> items = new Dictionary<int, string>();
for (int i = 1; i < iteration + 1; i++) // cosmetic +1 to avoid "Task N° 0"
{
items.Add(i, "Parallel Task N° " + i);
}
List<Task> tasks = new List<Task>();
// I guess we should use a Parallel.Foreach() here
foreach (var item in items)
{
// Creates a random interval to pause the thread at (up to 9 secs)
random.GetBytes(buffer);
int interval = new Random(BitConverter.ToInt32(buffer, 0)).Next(1000, 9000);
// http://stackoverflow.com/questions/5195486/
var temp = item;
var task = Task.Factory.StartNew(state =>
{
Console.WriteLine(String.Format(temp.Value + " will be completed in {0} miliseconds . . .", interval));
Thread.Sleep(interval);
return "The quick brown fox jumps over the lazy dog.";
}, temp.Value).ContinueWith(t => Console.WriteLine(t.AsyncState + " returned: " + t.Result));
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
Since you are calling task.Result
on each Task you are creating a Blocking Call that is causing the loop to wait until that task returns.
You should create all of the tasks first and then read the results in a separate loop.
精彩评论