0

Let's say I need the output from the below independent methods

var x = LongCalculation(123)
var y = LongCalculation2(345)
var z = LongCalculation3(678)
var b = LongCalculation4(910)

What is the least code invasive way of executing these calculations in parallel, without destroying the readability of the logic? I know of Parallel.Foreach() but in this case there is no list of items to process, only independent method calls.

user3666197
  • 1
  • 6
  • 50
  • 92
alenyb
  • 31
  • 3
  • If you specify the operation type inside `LongCalculations` it is much easy to answer your question. – Reza Heidari Apr 07 '22 at 11:31
  • After all administratively executed censoring deletions, you might miss the key property of the Problem-under-Review. All the calls achieve at most a just-[CONCURRENT] code-execution, as documented under Theodor's A. altogether with an MCVE-code as proof. Adding any "terminal"-blocking barrier introduces an artificial blocking of all paths of (so far independent,uncoordinated just-[CONCURRENT]) code-execution,which may & can align/release 'em once all-finished (Yet what happens if anyone blocks infinitely is easy to guess: Apollo 11 moon-landing would crash,wouldn't it?) yet still NOT parallel – user3666197 Apr 09 '22 at 07:26

3 Answers3

3

I guess the least invasive way is using Parallel.Invoke:

long x, y, z, b;
Parallel.Invoke(
    () => x = LongCalculation(123),
    () => y = LongCalculation2(345),
    () => z = LongCalculation3(678),
    () => b = LongCalculation4(910)
);
Clemens
  • 588
  • 6
  • 9
1

You can make a list of items to process out of your code:

int x,y,z,b;
List<Action> actions = new List<Action>
{
    () => x = LongCalculation(123),
    () => y = LongCalculation2(345),
    () => z = LongCalculation3(678),
    () => b = LongCalculation4(910)
};
Parallel.ForEach(actions, x => x.Invoke());

Online demo: https://dotnetfiddle.net/xy7sxF

SomeBody
  • 7,515
  • 2
  • 17
  • 33
  • 1
    This is a good approach. Something to have in mind is that in case of an exception in any of the 4 methods, the variables `x`, `y`, `z`, and `b` might end up being partially initialized. – Theodor Zoulias Apr 07 '22 at 11:57
0

Using the Task.Run and the Task.WaitAll should be quite readable:

var task1 = Task.Run(() => LongCalculation(123));
var task2 = Task.Run(() => LongCalculation2(345));
var task3 = Task.Run(() => LongCalculation3(678));
var task4 = Task.Run(() => LongCalculation4(910));
Task.WaitAll(task1, task2, task3, task4);
var x = task1.Result;
var y = task2.Result;
var z = task3.Result;
var b = task4.Result;

This assuming that limiting the degree of parallelism of the CPU-bound operations is not a requirement.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • Thanks but I found this very invasive and makes the logic much more difficult to understand. – alenyb Apr 07 '22 at 11:33
  • 1
    @alenyb yep, it's not the prettiest thing. The selling point of this approach is that the `x`, `y`, `z`, and `b` variables are assigned on the current thread. Which might be important if instead of variables they are actually properties of a non-thread-safe object. – Theodor Zoulias Apr 07 '22 at 11:36
  • What is an approximate add-on cost spent to enter one new item into a thread-pool Queue (as ref'd by the URL) at executing one `.Run()`-method in [ns]? What is the approximate add-on overhead cost of maintaining such a Queue Dispatcher ( governing task-scheduling and task-marshalling overhead operations so as to yield each task to actual working-thread once a thread-pool signals an empty thread becoming RTO ) in [ns] per 1 [ms]? You might also prefer to revise your text "...degree of parallelism...", while a code-execution flow is by far not parallel but a just-[CONCURRENT] w. a barrier added – user3666197 Apr 07 '22 at 21:23
  • 1
    @user3666197 I can't tell these exact metrics on top of my head. I know that the overhead of `Task.Run` is pretty small, to a point of being negligible for most practical applications. Feel free to experiment and report your measurements if you want. The other solutions proposed in this question (`Parallel.ForEach`/`Parallel.Invoke`) are using tasks too internally. The term "degree of parallelism" [is used by these commonly used APIs](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.paralleloptions.maxdegreeofparallelism), I didn't invent it myself. – Theodor Zoulias Apr 07 '22 at 21:33