The Parallel class was introduced in .Net Framework 4.0 and can run for and foreach loops in parallel.
Parallel Class
The Parallel class has three static methods:
- For – you can iterate collections in parallel using an index
- Foreach – this is the most used because you specify the collection and body of each loop.
- Invoke – you can use this method to run different actions in parallel.
Parallel Foreach vs Tasks
The Parallel class should be used when you want to run some CPU-intensive computation. Tasks should be used when you have I/O operations.
For example, you have a Bitmap of an image and want to change that picture’s color. This means that you have a big matrix, and you must change the value of each element. To do this operation, you will need CPU power.
public static Bitmap ChangeColor(Bitmap scrBitmap, Color color) { Color actualColor; Bitmap newBitmap = new Bitmap(scrBitmap.Width, scrBitmap.Height); for (int i = 0; i < scrBitmap.Width; i++) { for (int j = 0; j < scrBitmap.Height; j++) { actualColor = scrBitmap.GetPixel(i, j); if (BigComputation(actualColor)) { newBitmap.SetPixel(i, j, color); } } } return newBitmap; } public static Bitmap ChangeColorInParallel(Bitmap scrBitmap, Color color) { Bitmap newBitmap = new Bitmap(scrBitmap.Width, scrBitmap.Height); int width = scrBitmap.Width; int height = scrBitmap.Height; Object mLock = new Object(); Parallel.For(0, width, new ParallelOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount } , i => { for (int j = 0; j < height; j++) { Color actualColor = Color.Empty; lock (mLock) { actualColor = scrBitmap.GetPixel(i, j); } if (BigComputation(actualColor)) { lock (mLock) { newBitmap.SetPixel(i, j, color); } } } } ); return newBitmap; } private static bool BigComputation(Color actualColor) { //Some processing that takes time int r = actualColor.R; for (int i = 0; i < 1000; i++) { r = r * r + r * r + 1 + 22 * 10 + 2 * r + 3 * r + 4 * r; r = (((r - r - r) * r) == r - 2) ? r : (r - 1); } return actualColor.R < 50; } static void Main(string[] args) { Bitmap b1 = new Bitmap(@"C:\Users\valen\Desktop\big.jpg"); //Parallel var watch = System.Diagnostics.Stopwatch.StartNew(); Bitmap result = ChangeColorInParallel(b1, Color.Red); watch.Stop(); var elapsedMs = watch.ElapsedMilliseconds; Console.WriteLine(elapsedMs); // the result was 106775 // Sequential watch = System.Diagnostics.Stopwatch.StartNew(); result = ChangeColor(b1, Color.Red); watch.Stop(); elapsedMs = watch.ElapsedMilliseconds; Console.WriteLine(elapsedMs); //338017 }
On the other part, if you want to create a spider that will get the source code of some pages, you will need to use tasks.
You can create a more significant number of tasks than the number of processors because another task can be executed when a job is blocked.
class Program { static private readonly HttpClient _httpClient = new HttpClient(); static async Task<string> GetSource(string url) { using HttpResponseMessage response = await _httpClient.GetAsync(url); return await response.Content.ReadAsStringAsync(); } static async Task Main(string[] args) { List<string> urls = new List<string>() { "https://www.google.com/", "https://pastebin.com/D58Qd7TR", "https://pastebin.com/vQbtqxb9", "https://pastebin.com/u5q1YZMU", "https://pastebin.com/AfTmmz4D", "https://pastebin.com/QyN93hDz", "https://pastebin.com/TNAJpQvQ", "https://pastebin.com/rPjtZbRy", "https://pastebin.com/2GQZgecn" }; Task<string>[] tasks = urls.Select(u => GetSource(u)).ToArray(); string[] sources = await Task.WhenAll(tasks); _httpClient.Dispose(); } }
You can also use the Parallel class, but you will not get the fastest result. The HTTP request is an I/O operation, which blocks the task until it receives the response. Using the parallel extension approach, the execution thread will wait until it gets the response and the execution context is not transferred to another task.
PLINQ – Parallel LINQ
PLINQ offers methods that allow you to perform parallel execution on data sets.
Use these methods only when you use the CPU intensive. Parallel execution doesn’t mean that it will always be faster than the sequential method.
Here it’s an example of using the parallel method to determine how many prime numbers there are in an interval.
public static bool IsPrime(int number) { if (number <= 1) return false; if (number == 2) return true; if (number % 2 == 0) return false; int boundary = (int)Math.Floor(Math.Sqrt(number)); for (int i = 3; i <= boundary; i += 2) if (number % i == 0) return false; return true; } static async Task Main(string[] args) { var numbers = Enumerable.Range(0, 10000000); var watch = System.Diagnostics.Stopwatch.StartNew(); int count = numbers.AsParallel().Where(n => IsPrime(n)).Count(); watch.Stop(); var elapsedMs = watch.ElapsedMilliseconds; Console.WriteLine("Parallel method:"); Console.WriteLine($"There are {count} numbers"); Console.WriteLine($"{elapsedMs} duration"); watch = System.Diagnostics.Stopwatch.StartNew(); int count2 = numbers.Where(n => IsPrime(n)).Count(); watch.Stop(); var elapsedMs2 = watch.ElapsedMilliseconds; Console.WriteLine("Sequential method:"); Console.WriteLine($"There are {count2} numbers"); Console.WriteLine($"{elapsedMs2} duration"); }
The parallel method on my machine is four times faster than the sequential one.
Usually, the parallel approach works faster when you have extensive collections.