0

Nearly every introduction about async programming for C# warns against using the Sleep instruction, because it would block the whole thread.

But I found that during sleep, the Tasks from the queue are being fetched and executed. See:

    using System;
    using System.Threading.Tasks;

    namespace TestApp {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Main");
                Program.step1();

                for (int i = 0; i < 6; i++) {
                    System.Threading.Thread.Sleep(200);
                    Console.WriteLine("Sleep-Loop");
                }
            }

            private static async void step1() {
                await Task.Delay(400);
                Console.WriteLine("Step1");
                Program.step2();
            }

            private static async void step2() {
                await Task.Delay(400);
                Console.WriteLine("Step2");
            }
        }
    }

The output:

    Main
    Sleep-Loop
    Sleep-Loop
    Step1
    Sleep-Loop
    Sleep-Loop
    Step2
    Sleep-Loop
    Sleep-Loop

My questions:

  1. Is Sleep really allows the queued tasks to execute, or something else happens?
  2. If yes, then does this also happen in every other cases of idleness? For example during polling?
  3. In the above example, if we comment out the loop, then the application exits before any tasks could get executed. Is there another way to prevent that?
Crouching Kitten
  • 1,135
  • 12
  • 23
  • 2
    Your task execute on background thread, so while the main thread is executing (or sleeping), Step1 and 2 will execute on the background thread, and not the main thread. You can wait in the main program to finish task execution by using await. Await is like a thread.Join instruction, in this case it will wait for your task to be complete. – peeyush singh Mar 10 '19 at 01:05
  • @peeyushsingh That means that every interaction between the async and sync code require the thread-safety primitives, like locking? – Crouching Kitten Mar 10 '19 at 01:10
  • 1
    Locking is required when you are sharing data between multiple threads. Not whether you use async or multi-threading. Aysnc tries to encourage a different programming model, and you should try to make it lock free. – peeyush singh Mar 10 '19 at 01:24
  • Have a look at this: https://learn.microsoft.com/en-us/dotnet/csharp/async – peeyush singh Mar 10 '19 at 01:25
  • 1
    @CrouchingKitten no. You need to use synchronization primitives only when shared data accessed from different threads (as usual, nothing `Task` specific). I.e. if all code for tasks runs on the same thread (like default for WPF/WinForm app) than you don't need any additional synchronization. Consider reading [Task vs. Threads](https://stackoverflow.com/questions/4130194/what-is-the-difference-between-task-and-thread) and search about "Synchronization Context" – Alexei Levenkov Mar 10 '19 at 01:25
  • Don't use async void for pseudo parallelism, its bad design, and its not what its intended to be – TheGeneral Mar 10 '19 at 01:46

1 Answers1

2

In C# 7.3 you can have async entry points, I suggest using that.

Some notes :

  1. Don't use async void, it has subtleties with the way it deals with errors, if you see yourself writing async void then think about what you are doing. If it's not for an event handler you are probably doing something wrong
  2. If you want to wait for a bunch of tasks to finish, use Task.WhenAll

Modified example

static async Task Main(string[] args)
{
   Console.WriteLine("Start Task");
   var task = Program.step1();

   for (int i = 0; i < 6; i++)
   {
      await Task.Delay(100);
      Console.WriteLine("Sleep-Loop");
   }

   Console.WriteLine("waiting for the task to finish");
   await task;
   Console.WriteLine("finished");
   Console.ReadKey();
}

private static async Task step1()
{
   await Task.Delay(1000);
   Console.WriteLine("Step1");
   await Program.step2();
}

private static async Task step2()
{
   await Task.Delay(1000);
   Console.WriteLine("Step2");
}

It's important to note Tasks are not threads and async is not parallel, however they can be.

9 times out of 10 if you are using the async await pattern it is for IO bound work to use operating system I/O completion ports so you can free up threads. It's a scalability and UI responsiveness feature.

If you aren't doing any I/O work, then there is actually very little need for the async await pattern at all, and as such CPU work should probably be just wrapped in a Task.Run at the point of calling. Not wrapped in an async method.

At this point it's also good to note just using tasks are not the async and await pattern. Although they both have tasks in common, they are not the same thing.

Lastly, if you find you need to use asynchronous code in a fire and forget way, think very carefully how you will handle any errors.

Here are some guidelines.

  • If you want to do I/O work, use the async await pattern.
  • If you want to do CPU work use Task.Run.
  • Never use async void unless it's for an event handler.
  • Never wrap CPU work in an async method, let the caller use Task.Run
  • If you need to wait for a task, await it, never call Result, or Wait or use Task.WhenAll
halfer
  • 19,824
  • 17
  • 99
  • 186
TheGeneral
  • 79,002
  • 9
  • 103
  • 141
  • Thanks, now I switched completely to `.NET Core`, because `Mono` didn't allow the `async Task` Main method. So the strange parallel phenomenon was caused by that the tasks run on a "background thread". (I didn't know that all apps start 2 threads by default.) This `always return a Task` rule is very similar to returning promises in Javascript. – Crouching Kitten Mar 12 '19 at 11:40