Home > Back-end >  Don't understand behavior of Tasks in C#
Don't understand behavior of Tasks in C#

Time:05-07

I'm hoping someone can help me understand what seems like some straightforward code. Clearly, I'm missing something. There are 2 things I don't understand about the behavior of this code:

  1. If I let the code run, the last Debug.WriteLine for 'End Delay' never gets written. Why is this?

  2. If I put a breakpoint on the Task.WaitAll() line in Main(), I see that delayTask has a status of WaitingToRun and the 'Begin Delay' Debug.WriteLine in DoDelay() doesn't happen, no matter how long I wait. However, as soon as I F10 over the Task.WaitAll() line, I see 'Begin Delay' show up in output. Why does a breakpoint on the Task.WaitAll() line seems to prevent even beginning the task?

I'm running on .NET Framework 4.6.2, unfortunately, I don't have a choice about this.

Thanks to everyone who has posted. I have an answer for the 1st question above - I changed 'async void DoDelay()' to 'async Task DoDelay()'. But I still don't have any explanation for the behavior in my 2nd question.

static void Main(string[] args) 
{
    Debug.WriteLine($"Main-A: {DateTime.Now:mm:ss.fff}");
    Task delayTask = Task.Run(() => DoDelay(10000));
    Debug.WriteLine($"Main-B: {DateTime.Now:mm:ss.fff}");
    Task.WaitAll(delayTask);
}

static async void DoDelay(int delayMs) 
{
    Debug.WriteLine($"Begin Delay: {DateTime.Now:mm:ss.fff}");
    await Task.Delay(delayMs);
    Debug.WriteLine($"End Delay: {DateTime.Now:mm:ss.fff}");
}

CodePudding user response:

Task delayTask = Task.Run(() => DoDelay(10000));

This will call the method DoDelay(10000) on a background thread, and return a task that completes when the method returns. However, DoDelay will return at the first await statement. The rest of the method will be run at some later time, but there is nothing that will wait for this to occur.

To fix this you should make your method return a Task, i.e. async Task DoDelay(int delayMs). This will not affect when the method returns, but the returned task lets you wait for the entire method to complete. This can be made more simple by using a async main method:

static async Task Main(string[] args) {
    Debug.WriteLine($"Main-A: {DateTime.Now:mm:ss.fff}");
    var delayTask = DoDelay(10000);
    Debug.WriteLine($"Main-B: {DateTime.Now:mm:ss.fff}");
    await delayTask;
    Debug.WriteLine($"Main-C: {DateTime.Now:mm:ss.fff}");
}

static async Task DoDelay(int delayMs) {
    Debug.WriteLine($"Begin Delay: {DateTime.Now:mm:ss.fff}");
    await Task.Delay(delayMs);
    Debug.WriteLine($"End Delay: {DateTime.Now:mm:ss.fff}");
}

Only use async void when you absolutely have to, i.e. event handlers in an UI, and then make sure you are handling failures of any awaited tasks. In the vast majority of cases, use async Task or async Task<T>

CodePudding user response:

some Note: avoid using async void because you have to await on it to get result, So I change it to async Task

You have to use delayTask.Wait(); for waiting to complete the task.

Warning: delayTask.Wait() is bad practice use await delayTask; instead.

use this instead of your code I do some changes on it:

static void Main(string[] args) {

  Debug.WriteLine($"Main-A: {DateTime.Now:mm:ss.fff}");
  Task delayTask = Task.Run(async () => await DoDelay(10000));
  Debug.WriteLine($"Main-B: {DateTime.Now:mm:ss.fff}");
  delayTask.Wait();
}

static async Task DoDelay(int delayMs)
{
  Debug.WriteLine($"Begin Delay: {DateTime.Now:mm:ss.fff}");
  await Task.Delay(delayMs);
  Debug.WriteLine($"End Delay: {DateTime.Now:mm:ss.fff}");
}

you can also use

static async Task Main(string[] args) 

and remove Task.Run(async () => await DoDelay(10000)); and use await DoDelay(10000) and await delayTask; but it may not work it .NET framework 4.6.2

CodePudding user response:

The correct implementation of the async/await pattern in this case is as the code below

static class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine($"Main-A: {DateTime.Now:mm:ss.fff}");
        Task delayTask = Task.Run(async () => { await DoDelay(10000); });
        Task delayTaskSync = Task.Run(() => { DoDelaySync(10000); });
        Console.WriteLine($"Main-B: {DateTime.Now:mm:ss.fff}");
        Task.WaitAll(delayTask, delayTaskSync);
    }

    static async Task DoDelay(int delayMs)
    {
        Console.WriteLine($"Begin Delay: {DateTime.Now:mm:ss.fff}");
        await Task.Delay(delayMs);
        Console.WriteLine($"End Delay: {DateTime.Now:mm:ss.fff}");
    }

    static void DoDelaySync(int delayMs)
    {
        Console.WriteLine($"Begin Delay: {DateTime.Now:mm:ss.fff}");
        Task.Delay(delayMs).Wait();
        Console.WriteLine($"End Delay: {DateTime.Now:mm:ss.fff}");
    }
}

Here you can see an implementation with both an async function and a sync function. The Task.Run() run a parallel task and inside that task you can use more task with async/await or use synchronous function.

  • Related