Почему Task заканчивается даже в ожидании

У меня есть проблема в следующем коде:

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!!");
}

Я просто хочу получить Exception из новой запущенной задачи MainTask. Но результат не был тем, чего я ожидал.

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

Как вы можете видеть результат, задача заканчивается до "Ожидание завершено!!" консольный журнал. У меня нет подсказки, почему MainTask закончился, даже если внутри MainTask есть команда await внутри? Я что-то пропустил?

Ответы

Ответ 1

Task.Factory.StartNew не понимает делегатов aync, поэтому вам нужно использовать Task.Run в этом случае, и исключение должно протекать через.

Task.Factory.StartNew(MainTask);

по существу эквивалентно

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

который игнорирует возвращенную задачу из MainTask, и исключение просто проглатывается.

Подробнее см. в сообщении в блоге.

Попробуйте использовать Task.Run вместо этого, и вы получите свое исключение:

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!!");
}

Ответ 2

Здесь есть отличные ответы, но я хотел бы указать на очевидное: Task.Factory.StartNew полностью избыточно, ненужно и используется неправильно.

Если вы замените

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

с

Task newTask = MainTask();

Вы получите именно то поведение, которое ожидаете, не тратя лишний поток нитки, чтобы запустить другой поток threadpool. На самом деле, если вы хотите переписать свой пример, чтобы быть более идиоматичным, вы бы использовали что-то вроде этого:

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!!");
}

Этот код использует поток потока потока для кода после задержки и повторяет исключение в вызове task.Wait() - вы, возможно, захотите сделать что-то еще, конечно.

В качестве побочного примечания, даже если вы не хотите явно ждать завершения задачи, вы не должны использовать while (true) {}, чтобы предотвратить завершение работы приложения. Простое Console.ReadLine() будет работать так же хорошо, и не будет подталкивать один из ваших ядер процессора до 100% использования.

Ответ 3

Я изменил вашу проблему здесь, чтобы поймать исключения.

    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!!");


    }

вы должны использовать TaskContinuationOptions.NotOnFaulted, что означает, что продолжение с задачей будет выполняться только в том случае, если родительская задача не имела никаких исключений.