3

I'm looking for a simple C# example of a pattern that meets the following requirements:

  1. I want to execute 2 methods asynchronously, initiating the second without waiting for the first to finish.
  2. Each method accepts a parameter and returns and output value.
  3. I then want to wait for the results of both before moving on.

This seemed like a scenario where I could easily use the async and await keywords but after looking at a couple of Hello World examples, I was surprised that I could not easily meet all of the above criteria at the same time. For example, while using the await keyword kept my UI from being non blocking and allowing my app to continue to be responsive to events while I await the completion of a method, the methods were still executing synchronously. Or if I was able to execute asynchronously, I had trouble trying to pass an input parameter to the calling method and receiving a result back.

The following seems to meet my requirements and leads me to believe that I'd be able to make great use out of it but first I'd like to get your feedback on how it can be improved.

I've played around with Threads before and found that make my code much more complex and was expecting that the "async and await" keywords would make my life easier but the following code which appears to meet my requirements does not even use these keywords. Am I using the "Parallel Task Library" here? If so, how would you contrast the functionality in this library to the async and await functionality?

using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace ASyncCourse
{
    static class Program
    {
        static void Main()
        {
            int result1 = 0;
            int result2 = 0;

            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();

            Task task1 = Task.Run(() => result1 = DoSomeWork(1));
            Debug.WriteLine($"After Task1: {stopWatch.Elapsed}");

            Task task2 = Task.Run(() => result2 = DoSomeOtherWork(10));
            Debug.WriteLine($"After Task2: {stopWatch.Elapsed}");

            Task.WaitAll(task1, task2);
            stopWatch.Stop();

            int sum;
            
            sum = result1 + result2; 
            Debug.WriteLine($"Sum: {sum}");
            Debug.WriteLine($"Final: {stopWatch.Elapsed}");
        }

        private static int DoSomeOtherWork(int waitFor)
        {
            Thread.Sleep(waitFor * 1000);
            return waitFor;
        }

        private static int DoSomeWork(int waitFor)
        {
            Thread.Sleep(waitFor * 1000);
            return waitFor;
        }
    }
}
ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
Chad
  • 23,658
  • 51
  • 191
  • 321
  • Ideally you wan async/await to go from the top to the bottom (unless you're dealing with a CPU bound algorithm). In your example you could make your methods async by using `Task.Delay` instead of `Thread.Sleep` and then you wouldn't need the `Task.Run`. Also you should make the `Main` async and await the `Task.WhenAll`. – juharr Jan 04 '21 at 03:30
  • The code you posted seems to meet your stated requirements. If you are having trouble recomposing the approach to use actual `async` methods instead of `Task.Run()`, your question needs to show _that code_. It's not at all clear what you need help with, nor why none of the thousands of async/await questions already on the site don't address your concern. – Peter Duniho Jan 04 '21 at 03:51

4 Answers4

12

You can use the async and await pattern and await the cpu bound tasks, however you will need to use async Task Main() entry point

public static async Task Main()
{
    var task1 = Task.Run(() => CpuWork1(1));
    var task2 = Task.Run(() => CpuWork2(10));
    
    var result1 = await task1;
    var result2 = await task2;

    // or

    var results = await Task.WhenAll(task1, task2) ;
}

If your workloads are IO bound, then they would have the async Task<T> signature, and you would just await the methods returned tasks, similar to above (and not use Task.Run)

Complete Example

static async Task Main()
{

   var stopWatch = new Stopwatch();
   stopWatch.Start();

   var task1 = Task.Run(() =>  DoSomeWork(1));
   var task2 = Task.Run(() =>  DoSomeOtherWork(10));

   var results = await Task.WhenAll(task1, task2);
   stopWatch.Stop();

   Debug.WriteLine($"Sum: {results.Sum()}");
   Debug.WriteLine($"Final: {stopWatch.Elapsed}");
}

Or similarly with an async methods

static async Task Main()
{

   var stopWatch = new Stopwatch();
   stopWatch.Start();

   var task1 = DoSomeWorkAsync(1);
   var task2 = DoSomeOtherWorkAsync(10);

   var results = await Task.WhenAll(task1, task2);
   stopWatch.Stop();

   Debug.WriteLine($"Sum: {results.Sum()}");
   Debug.WriteLine($"Final: {stopWatch.Elapsed}");
}

private static async Task<int> DoSomeOtherWorkAsync(int waitFor)
{
   // some IO bound workload
   await Task.Delay(waitFor * 1000);
   return waitFor;
}

private static async Task<int> DoSomeWorkAsync(int waitFor)
{
   // some IO bound workload
   await Task.Delay(waitFor * 1000);
   return waitFor;
}
TheGeneral
  • 79,002
  • 9
  • 103
  • 141
  • Could you please provide a complete working example? I'm too much of a noob and I'm getting errors that I don't know how to resolve. – Chad Jan 04 '21 at 03:53
3

Asynchronous methods are method designed to access external resources without blocking and without wasting resources(extra threads)

Use Task.Delay(1000) to simulate asynchronous method.

private async Task<int> LoadSomething(int value)
{
    await Task.Delay(value * 1000);
    return value;
}

Then

var task1 = LoadSomething(2); 
// executes next line without waiting for previous line to complete
var task2 = LoadSomething(5); 

await Task.WhenAll(task1, task2); // takes only 5 seconds

var result1 = task1.Result;
var result2 = task2.Result;
Fabio
  • 31,528
  • 4
  • 33
  • 72
  • An alternative to `var result1 = task1.Result;` is the `var result1 = await task1;` syntax. It may be a good idea to adopt it from maintenance perspective, because the `.Result` property is associated with deadlock problems, potentially causing red flags and false alarms every time an eye is passing over it. – Theodor Zoulias Jan 04 '21 at 04:00
2

Use async on all of your methods and have them return Task.

Call await Task.Delay instead of Thread.Sleep

Then, you can use await Task.WhenAll to wait for your tasks to complete.

If you do not put await in front of your method calls, execution proceeds without waiting for a result.

You can call Result to get the result once you have awaited them.

static async Task Main()
{
    Stopwatch stopWatch = new Stopwatch();
    stopWatch.Start();

    Task<int> task1 = DoSomeWork(1);
    Console.WriteLine($"After Task1: {stopWatch.Elapsed}");

    Task<int> task2 = DoSomeOtherWork(10);
    Console.WriteLine($"After Task2: {stopWatch.Elapsed}");

    await Task.WhenAll(task1, task2);
    stopWatch.Stop();

    int sum = task1.Result + task2.Result;
    Console.WriteLine($"Sum: {sum}");
    Console.WriteLine($"Final: {stopWatch.Elapsed}");
}

private static async Task<int> DoSomeOtherWork(int waitFor)
{
    await Task.Delay(waitFor * 1000);
    return waitFor;
}

private static async Task<int> DoSomeWork(int waitFor)
{
    await Task.Delay(waitFor * 1000);
    return waitFor;
}

output

After Task1: 00:00:00.0037888
After Task2: 00:00:00.0094963
Sum: 11
Final: 00:00:10.0126715
Todd Skelton
  • 6,839
  • 3
  • 36
  • 48
1

you use await keyword. async operations perform parallelly. when we need to wait until we get the result use await before it.

Example:

var task1 = AsyncOperation1(2);
var task2 = AsyncOperation1(5);


var result1 = await task1;
var result2 = await task2;

use last two line instead of Task.WhenAll(task1, task2);

Shahid_bd
  • 19
  • 3
  • 2
    _"async operations perform parallelly."_ - are you sure about that? – ProgrammingLlama Jan 04 '21 at 03:48
  • [These are async methods but they're not executed in parallel](https://rextester.com/BKD21673) (I'm using `Thread.Sleep` here to simulate CPU-bound work). See [here](https://stackoverflow.com/a/28068617/3181933) for why. – ProgrammingLlama Jan 04 '21 at 03:55
  • i means execute at a time in different thread. – Shahid_bd Jan 04 '21 at 13:17
  • Sorry, I messed up my example a little. [I've fixed it](https://rextester.com/GXWZR4245). The point is that async doesn't necessarily mean that your task runs in a different thread. In the example I've provided, everything is running synchronously in the same thread as the calling method. You can compare it to what happens when we force a different thread with `Task.Run` in [this example](https://rextester.com/UAFGX30971). – ProgrammingLlama Jan 04 '21 at 13:49
  • And in a [third example](https://rextester.com/FNIX98150) you can see that by suspending the asynchronous method (until the delay is elapsed) that we actually see the task method starting in one thread (synchronous part) and then (after the delay is complete) resuming in a different thread. I recommend reading [Stephen Cleary's blog](https://blog.stephencleary.com/2013/11/there-is-no-thread.html) on the topic of threads vs tasks. – ProgrammingLlama Jan 04 '21 at 13:56
  • thanks @John. i will go through later. – Shahid_bd Jan 04 '21 at 14:34