3

I have 8 methods in an ASP.NET Console App, like Fun1(), Fun2(), Fun3() ... and so on. In my console application I have called all these methods sequentially. But now the requirement is I have do that using parallel programming concepts. I have read about task and threading concepts in Java but completely new to .NET parallel Programming.

Here is the flow of methods I needed in my console app,

Diagram

As you can see the diagram, Task1 and Task2 should run in parallel, and Task3 will only occur after completion of the previous two.

The functions inside each task, for example Fun3 and Fun4 for the Task1, should run sequentially, the one after the other.

Can anyone please help me out?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • you can use Parallel.Invoke(). [For more details](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming?redirectedfrom=MSDN) – Pradeep Kumar Oct 16 '22 at 19:11
  • 1
    *"...in an ASP.NET Console App..."* -- As far as I know console applications and ASP.NET applications are two different types of projects. – Theodor Zoulias Oct 16 '22 at 19:26
  • The `Fun4` should run after the completion of the `Fun3`, or concurrently with the `Fun3`? – Theodor Zoulias Oct 16 '22 at 19:35
  • 2
    @TheodorZoulias Not really. Starting with ASP.NET Web API, and continuing with ASP.NET Core, the capability is built in to "self-host" - you can just run as a console app. You could start from the console template and build up to ASP.NET Core as you need it, or start with the ASP.NET Core template and remove everything until you're left with a small console app. It's likely not how you'd do things in production, but the line is really blurred between web and console apps. Very different from the old ASP.NET on .NET Framework days. – mason Oct 16 '22 at 21:12
  • @TheodorZoulias no no Fun4 can run after the completion of fun3. – Suprateem Bose Oct 17 '22 at 04:37
  • 1
    @SuprateemBose I edited the question to clarify this point. – Theodor Zoulias Oct 17 '22 at 05:10
  • @SuprateemBose what do those methods *really* do? In a web application, parallelism can *hurt* scalability because the code is using a thread/core that could server another request. If `Fun1()` etc execute asynchronous IO it makes sense to use `WhenAll()` to await for the already async methods to complete. If they use the CPU though, running these in parallel means you use 2 threads to server 1 request, halving the number of concurrent requests you can serve. In high traffic systems that's worse than it sounds, because requests will start queueing and spilling to other servers in the farm – Panagiotis Kanavos Oct 17 '22 at 09:55

2 Answers2

3

One way to solve this is, by using WhenAll.

To take an example, I have created X number of methods with the name FuncX() like this:

async static Task<int> FuncX()
{
    await Task.Delay(500);
    var result = await Task.FromResult(1);
    return result;
}

In this case, we have Func1, Func3, Func4, Func5, and Func6.

So we call methods and pass them to a list of Task.

var task1 = new List<Task<int>>();

task1.Add(Func3());
task1.Add(Func4());

var task2 = new List<Task<int>>();

task2.Add(Func1());
task2.Add(Func5());
task2.Add(Func6());

You have 2 options to get the result:

// option1
var eachFunctionIsDoneWithAwait1 = await Task.WhenAll(task1);
var eachFunctionIsDoneWithAwait2 = await Task.WhenAll(task2);
var sum1 = eachFunctionIsDoneWithAwait1.Sum() + eachFunctionIsDoneWithAwait2.Sum();
Console.WriteLine(sum1);

// option2
var task3 = new List<List<Task<int>>>();
task3.Add(task1);
task3.Add(task2);
var sum2 = 0;
task3.ForEach(async x =>
{
    var r = await Task.WhenAll(x);
    sum2 += r.Sum();
});

Console.WriteLine(sum2);

This is just example for inspiration, you can change it and do it the way you want.

Maytham Fahmi
  • 31,138
  • 14
  • 118
  • 137
3

Here is how you could create the tasks according to the diagram, using the Task.Run method:

Task task1 = Task.Run(() =>
{
    Fun3();
    Fun4();
});
Task task2 = Task.Run(() =>
{
    Fun1();
    Fun5();
    Fun6();
});
Task task3 = Task.Run(async () =>
{
    await Task.WhenAll(task1, task2);
    Fun7();
    Fun8();
});

The Task.Run invokes the delegate on the ThreadPool, not on a dedicated thread. If you have some reason to create a dedicated thread for each task, you could use the advanced Task.Factory.StartNew method with the TaskCreationOptions.LongRunning argument, as shown here.

It should be noted that the above implementation has not an optimal behavior in case of exceptions. In case the Fun3() fails immediately, the optimal behavior would be to stop the execution of the task2 as soon as possible. Instead this implementation will execute all three functions Fun1, Fun5 and Fun6 before propagating the error. You could fix this minor flaw by creating a CancellationTokenSource and invoking the Token.ThrowIfCancellationRequested after each function, but it's going to be messy.

Another issue is that in case both task1 and task2 fail, only the exception of the task1 is going to be propagated through the task3. Solving this issue is not trivial.


Update: Here is one way to solve the issue of partial exception propagation:

Task task3 = Task.WhenAll(task1, task2).ContinueWith(t =>
{
    if (t.IsFaulted)
    {
        TaskCompletionSource tcs = new();
        tcs.SetException(t.Exception.InnerExceptions);
        return tcs.Task;
    }
    if (t.IsCanceled)
    {
        TaskCompletionSource tcs = new();
        tcs.SetCanceled(new TaskCanceledException(t).CancellationToken);
        return tcs.Task;
    }
    Debug.Assert(t.IsCompletedSuccessfully);
    Fun7();
    Fun8();
    return Task.CompletedTask;
}, default, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default)
    .Unwrap();

In case both task1 and task2 fail, the task3 will propagate the exceptions of both tasks.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • I don't quite understand why `Task.WhenAll()` throws only an exception from `task1`. According to the msdn documentation - If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state, where **its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks** - then should't it also throw an exception from `task2`? – Qwertyluk Oct 17 '22 at 07:20
  • 1
    @Qwertyluk the `Task.WhenAll` propagates all the exceptions, but the `await` propagates [only the first](https://stackoverflow.com/questions/12007781/why-doesnt-await-on-task-whenall-throw-an-aggregateexception). – Theodor Zoulias Oct 17 '22 at 07:27