Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Task finishes even in await

I have a problem in the following code:

static void Main (string[] args)
{
    Task newTask = Task.Factory.StartNew(MainTask);
    newTask.ContinueWith ((Task someTask) => 
    {
        Console.WriteLine ("Main State=" + someTask.Status.ToString () + " IsFaulted=" + someTask.IsFaulted+" isComplete="+someTask.IsCompleted);
    });
    while (true) 
    {

    }
}

static async Task MainTask()
{
    Console.WriteLine ("MainStarted!");
    Task someTask = Task.Factory.StartNew (() => 
    {
        Console.WriteLine ("SleepStarted!");
        Thread.Sleep(1000);
        Console.WriteLine ("SleepEnded!");
    });
    await someTask;
    Console.WriteLine ("Waiting Ended!!");
    throw new Exception ("CustomException!");
    Console.WriteLine ("NeverReaches here!!");
}

I just want to get Exception from new started task MainTask. But the result was not what I was expected.

MainStarted!
Main State = RanToCompletion IsFaulted = False isComplete = True
SleepStarted!
SleepEnded!
Waiting Ended!!

As you can see the result, task finishes before "Waiting Ended!!" console log. I don't have a clue that why MainTask ended even if in MainTask has await command inside? Did I missed something?

like image 627
Sunny Kim Avatar asked Sep 04 '15 05:09

Sunny Kim


People also ask

Why you shouldn't use async void?

Async void methods can wreak havoc if the caller isn't expecting them to be async. When the return type is Task, the caller knows it's dealing with a future operation; when the return type is void, the caller might assume the method is complete by the time it returns.

What happens if you do not await a Task?

If you don't await the task or explicitly check for exceptions, the exception is lost. If you await the task, its exception is rethrown. As a best practice, you should always await the call. By default, this message is a warning.

Is async but does not return a Task?

Async methods that don't contain a return statement or that contain a return statement that doesn't return an operand usually have a return type of Task. Such methods return void if they run synchronously.

What is the difference between Task run and async await?

Async methods are intended to be non-blocking operations. An await expression in an async method doesn't block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method.


3 Answers

Task.Factory.StartNew does not understand async delegates so you need to use Task.Run in this case and the exception should flow through.

Task.Factory.StartNew(MainTask);

is essentially equivalent to

Task.Factory.StartNew(() => MainTask);

which ignores the returned task from MainTask and the exception just gets swallowed.

See this blog post for more details.

Try using Task.Run instead and you'll get your exception:

void Main(string[] args)
{
    Task newTask = Task.Run(MainTask);
    newTask.ContinueWith((Task someTask) =>
   {
       Console.WriteLine("Main State=" + someTask.Status.ToString() + " IsFaulted=" + someTask.IsFaulted + " isComplete=" + someTask.IsCompleted);
   });
    while (true)
    {

    }
}

static async Task MainTask()
{
    Console.WriteLine("MainStarted!");
    Task someTask = Task.Run(() =>
   {
       Console.WriteLine("SleepStarted!");
       Thread.Sleep(1000);
       Console.WriteLine("SleepEnded!");
   });
    await someTask;
    Console.WriteLine("Waiting Ended!!");
    throw new Exception("CustomException!");
    Console.WriteLine("NeverReaches here!!");
}
like image 98
NeddySpaghetti Avatar answered Sep 21 '22 23:09

NeddySpaghetti


There's great answers here, but I'd like to point out the obvious - the Task.Factory.StartNew is completely redundant, unnecessary and used wrong.

If you replace

Task newTask = Task.Factory.StartNew(MainTask);

with

Task newTask = MainTask();

You'll get exactly the behaviour you expect, without wasting yet another threadpool thread just to start another threadpool thread. In fact, if you wanted to rewrite your example to be more idiomatic, you'd use something like this:

static void Main (string[] args)
{
    var task = 
      MainTask()
      .ContinueWith(t => Console.WriteLine("Main State={0}", t.Status));

    task.Wait();
}

static async Task MainTask()
{
    Console.WriteLine ("MainStarted!");

    await Task.Delay(1000);

    Console.WriteLine ("Waiting Ended!!");

    throw new Exception ("CustomException!");

    Console.WriteLine ("NeverReaches here!!");
}

This code only uses a thread pool thread for the code after the delay, and rethrows the exception on the task.Wait() call - you might want to do something else, of course.

As a side-note, even if you don't want to explicitly wait for the task to complete, you shouldn't use while (true) {} to prevent the application from terminating - a simple Console.ReadLine() will work just as well, and isn't going to push one of your CPU cores to 100% utilization :)

like image 40
Luaan Avatar answered Sep 21 '22 23:09

Luaan


I modified your problem here to catch the exceptions.

    static void Main(string[] args)
    {
        DoFoo();
        Console.ReadKey();
    }



    static async void DoFoo()
    {
        try
        {
            await Foo();
        }
        catch (Exception ex)
        {
            //This is where you can catch your exception
        }
    }




    static async Task Foo()
    {

        await MainTask().ContinueWith((Task someTask) =>
        {

            Console.WriteLine("Main State=" + someTask.Status.ToString() + " IsFaulted=" + someTask.IsFaulted + " isComplete=" + someTask.IsCompleted);

        }, TaskContinuationOptions.NotOnFaulted);

    }

    static async Task MainTask()
    {


        Console.WriteLine("MainStarted!");
        Task someTask = Task.Run(() =>
        {
            Console.WriteLine("SleepStarted!");
            Thread.Sleep(1000);
            Console.WriteLine("SleepEnded!");
        });
        await someTask;
        throw new Exception("CustomException!");

        Console.WriteLine("Waiting Ended!!");


    }

you should use TaskContinuationOptions.NotOnFaulted which means that the continue with task will only execute if the parent task did not had any exceptions.

like image 41
Rohit Avatar answered Sep 21 '22 23:09

Rohit