146

I've been trying for a while to get something I thought would be simple working with .NET 4.5

I want to fire off two long running tasks at same time and collect the
results in in the best C# 4.5 (RTM) way

The following works but I don't like it because:

  • I want Sleep to be an async method so it can await other methods
  • It just looks clumsy with Task.Run()
  • I don't think this is even using any new language features at all!

Working code:

public static void Go()
{
    Console.WriteLine("Starting");

    var task1 = Task.Run(() => Sleep(5000));    
    var task2 = Task.Run(() => Sleep(3000));

    int totalSlept = task1.Result + task2.Result;

    Console.WriteLine("Slept for a total of " + totalSlept + " ms");
}

private static int Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    Thread.Sleep(ms);
    Console.WriteLine("Sleeping for " + ms + " FINISHED");
    return ms;
}

Non working code:

Update: This actually works and is the correct way to do it, the only problem is the Thread.Sleep

This code doesn't work because the call to Sleep(5000) immediately starts the task running so Sleep(1000) doesn't run until it has completed. This is true even though Sleep is async and I'm not using await or calling .Result too soon.

I thought maybe there is a way to get a non-running Task<T> by calling an async method so I could then call Start() on the two tasks, but I can't figure out how to get a Task<T> from calling an async method.

public static void Go()
{
    Console.WriteLine("Starting");

    var task1 = Sleep(5000);    // blocks
    var task2 = Sleep(1000);

    int totalSlept = task1.Result + task2.Result;

    Console.WriteLine("Slept for " + totalSlept + " ms");
}

private static async Task<int> Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    Thread.Sleep(ms);
    return ms;
}
abatishchev
  • 98,240
  • 88
  • 296
  • 433
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689

6 Answers6

152
async Task<int> LongTask1() { 
  ...
  return 0; 
}

async Task<int> LongTask2() { 
  ...
  return 1; 
}

...
{
   Task<int> t1 = LongTask1();
   Task<int> t2 = LongTask2();
   await Task.WhenAll(t1,t2);
   //now we have t1.Result and t2.Result
}
Bart
  • 2,167
  • 2
  • 15
  • 21
  • 4
    I +1 because you declare t1,t2 as Task, which is the right way. – Won Feb 20 '13 at 20:20
  • 12
    I believe this solution requires the Go method to also be async, meaning it exposes the ability to be asynchronous. If you wanted something more like the askers case where the caller's `Go` method is synchronous, but wants to complete two independent tasks asynchronously(i.e. neither needs to complete before the other, but both must complete before executions continues) then **`Task.WaitAll`** would be better, and you don't need the await keyword, thus don't need the calling `Go` method to be async itself. **Neither approach is better, it's just a matter of what your goal is.** – AaronLS Jul 01 '13 at 18:17
  • 1
    Void methode: `async void LongTask1() {...}` has no Task.Result property. Use Task without T in such case: `async Task LongTask1()`. – Arvis Dec 14 '13 at 20:29
  • I didn't get the results from either one of the tasks. So I changed it to `Task t1 = LongTask1();` and now I get `t1.Result`. `` is the returning type of your result. You'll need a `return ` in your method for this to work. – gilu Apr 17 '18 at 04:28
  • @gilu you are obviously right, main point of the snippet was to present how to wait until both has completed, but i mentioned Result property in comment. I made small change, please take a look on the snippet now. – Bart Apr 18 '18 at 07:23
  • 1
    It may be worth mentioning that if you are doing some really simple things and don't want the extra `t1` and `t2` variables, you can use `new Task(...)`. For example: `int int1 = 0; await Task.WhenAll(new Task(async () => { int1 = await LongTask1(); }));`. One catch of this approach is that the compiler will not recognize that the variable was assigned to and will treat it as unassigned if you do not give it an initial value. – Robert Dennis Nov 08 '18 at 21:23
108

You should use Task.Delay instead of Sleep for async programming and then use Task.WhenAll to combine the task results. The tasks would run in parallel.

public class Program
    {
        static void Main(string[] args)
        {
            Go();
        }
        public static void Go()
        {
            GoAsync();
            Console.ReadLine();
        }
        public static async void GoAsync()
        {

            Console.WriteLine("Starting");

            var task1 = Sleep(5000);
            var task2 = Sleep(3000);

            int[] result = await Task.WhenAll(task1, task2);

            Console.WriteLine("Slept for a total of " + result.Sum() + " ms");

        }

        private async static Task<int> Sleep(int ms)
        {
            Console.WriteLine("Sleeping for {0} at {1}", ms, Environment.TickCount);
            await Task.Delay(ms);
            Console.WriteLine("Sleeping for {0} finished at {1}", ms, Environment.TickCount);
            return ms;
        }
    }
softveda
  • 10,858
  • 6
  • 42
  • 50
  • 15
    This is a great answer... but I thought this wrong was answer until I ran it. then I understood. It really does execute in 5 seconds. The trick is to NOT await the tasks immediately, instead await on Task.WhenAll. – Tim Lovell-Smith Apr 03 '14 at 09:13
4

While your Sleep method is async, Thread.Sleep is not. The whole idea of async is to reuse a single thread, not to start multiple threads. Because you've blocked using a synchronous call to Thread.Sleep, it's not going to work.

I'm assuming that Thread.Sleep is a simplification of what you actually want to do. Can your actual implementation be coded as async methods?

If you do need to run multiple synchronous blocking calls, look elsewhere I think!

Richard
  • 29,854
  • 11
  • 77
  • 120
  • thanks Richard - yes it seems to work as expected when I actually use my service call – Simon_Weaver Sep 09 '12 at 21:38
  • then how to run async? I have application that do lot of file switching and wait for file, around 5 second, and then another process, when i "when for all" it first run first, then second, even though i said: `var x = y()`, and not `var x=await y()` or `y().wait()` yet still it wait all the way, and if async isn't handle that by itself, what should i do? NOTE that y is decorated with async, and i expect it to do all within the when all, not right on where it is assigned, EDIT: i just when to my partner said, let's try `Task.Factory`, and he said it worked when i break out side of this class – Hassan Faghihi Dec 12 '17 at 06:49
2

To answer this point:

I want Sleep to be an async method so it can await other methods

you can maybe rewrite the Sleep function like this:

private static async Task<int> Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    var task = Task.Run(() => Thread.Sleep(ms));
    await task;
    Console.WriteLine("Sleeping for " + ms + "END");
    return ms;
}

static void Main(string[] args)
{
    Console.WriteLine("Starting");

    var task1 = Sleep(2000);
    var task2 = Sleep(1000);

    int totalSlept = task1.Result +task2.Result;

    Console.WriteLine("Slept for " + totalSlept + " ms");
    Console.ReadKey();
}

running this code will output :

Starting
Sleeping for 2000
Sleeping for 1000
*(one second later)*
Sleeping for 1000END
*(one second later)*
Sleeping for 2000END
Slept for 3000 ms
Liam
  • 27,717
  • 28
  • 128
  • 190
asidis
  • 1,374
  • 14
  • 24
2

It's weekend now!

    public async void Go()
    {
        Console.WriteLine("Start fosterage...");

        var t1 = Sleep(5000, "Kevin");
        var t2 = Sleep(3000, "Jerry");
        var result = await Task.WhenAll(t1, t2);

        Console.WriteLine($"My precious spare time last for only {result.Max()}ms");
        Console.WriteLine("Press any key and take same beer...");
        Console.ReadKey();
    }

    private static async Task<int> Sleep(int ms, string name)
    {
            Console.WriteLine($"{name} going to sleep for {ms}ms :)");
            await Task.Delay(ms);
            Console.WriteLine("${name} waked up after {ms}ms :(";
            return ms;
    }
Arvis
  • 8,273
  • 5
  • 33
  • 46
0

This article helped explain a lot of things. It's in FAQ style.

Async/Await FAQ

This part explains why Thread.Sleep runs on the same original thread - leading to my initial confusion.

Does the “async” keyword cause the invocation of a method to queue to the ThreadPool? To create a new thread? To launch a rocket ship to Mars?

No. No. And no. See the previous questions. The “async” keyword indicates to the compiler that “await” may be used inside of the method, such that the method may suspend at an await point and have its execution resumed asynchronously when the awaited instance completes. This is why the compiler issues a warning if there are no “awaits” inside of a method marked as “async”.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689