Почему ожидание холодной задачи не выдает

#c# #asynchronous #async-await

#c# #асинхронный #async-await

Вопрос:

Я просто экспериментировал, чтобы посмотреть, что происходит, когда ожидается холодная задача (то есть, Task которая не была запущена). К моему удивлению, код просто завис навсегда, и «Finsihed» никогда не печатается. Я бы ожидал, что генерируется исключение.

 public async Task Test1()
{
    var task = new Task(() => Thread.Sleep(1000));
    //task.Start();
    await task;
}

void Main()
{
    Test1().Wait();
    Console.WriteLine("Finished");
}
  

Тогда я подумал, что, возможно, задачу можно запустить из другого потока, поэтому я изменил код на:

 public async Task Test1()
{
    var task = new Task(() => Thread.Sleep(1000));
    //task.Start();
    await task;

    Console.WriteLine("Test1 Finished");
}

void Main()
{
    var task1 = Test1();

    Task.Run(() => 
    {
        Task.Delay(5000);   
        task1.Start();
    });

    task1.Wait();
    Console.WriteLine("Finished");
}
  

Но он по-прежнему заблокирован task1.Wait() . Кто-нибудь знает, есть ли способ запустить холодную задачу после ее ожидания?

В противном случае кажется, что нет смысла выполнять await холодную задачу, поэтому, возможно, задача должна быть либо запущена, когда ожидается, либо должно быть выдано исключение.

Обновить

Я ожидал неправильной задачи, то есть внешней задачи, возвращаемой, Test1 а не той, которая была создана внутри нее. Исключение InvalidOperationException, упомянутое @Jon Skeet, выбрасывалось внутри Task.Run , однако, поскольку результирующая задача не была выполнена, исключение не было сгенерировано в основном потоке. Ввод try/catch внутри Task.Run или вызов Wait() or Result для задачи, возвращаемой, вызвал Task.Run исключение в главном потоке консоли.

Ответ №1:

Вы пытаетесь запустить задачу, возвращенную методом async — это не та «холодная» задача, с которой вы начинали. Действительно, если вы добавите некоторую диагностику к своему Task.Run вызову, вы увидите, что генерируется исключение:

Система.Исключение InvalidOperationException: Start не может быть вызван для задачи в стиле promise.

Вот пример, показывающий, что, я думаю, вы действительно пытались сделать:

 using System;
using System.Threading;
using System.Threading.Tasks;

public class Test
{
    static void Main(string[] args)
    {
        // Not using Task.Delay! That would be pointless
        Task t1 = new Task(() => Thread.Sleep(1000));
        Task t2 = Await(t1);
        Console.WriteLine(t2.Status);
        Console.WriteLine("Starting original task");
        t1.Start(); 
        Console.WriteLine(t2.Status);
        t2.Wait();
        Console.WriteLine(t2.Status);        
    }

    static async Task Await(Task task)
    {
        Console.WriteLine("Beginning awaiting");
        await task;
        Console.WriteLine("Finished awaiting");        
    }
}
  

Обратите внимание на использование Thread.Sleep вместо Task.Delay ; если вы не используете результат Task.Delay , он в основном ничего не делает. Использование Thread.Sleep — это эмуляция реальной работы, которая должна быть выполнена в другой задаче.

Что касается того, почему ожидание незавершенной задачи не генерирует исключение — я думаю, что это разумно, если честно. Это позволяет допустимым ситуациям, подобным приведенным выше, что в некоторых случаях может облегчить жизнь. (Например, вы можете создать много задач перед их запуском, и вы можете захотеть начать ждать их завершения, прежде чем запускать их.)

Комментарии:

1. Спасибо, Джон, я думаю, я немного запутался

Ответ №2:

Кто-нибудь знает, есть ли способ запустить холодную задачу после ее ожидания?

Вы все равно можете создать холодную задачу из async метода и запустить ее позже, если это то, что вы хотите:

 class Program
{
    public static async Task Test1()
    {
        await Task.Delay(1000);
        Console.WriteLine("Test1 is about to finish");
    }

    static void Main(string[] args)
    {
        var taskOuter = new Task<Task>(Test1);
        var taskInner = taskOuter.Unwrap();

        Task.Run(() =>
        {
            Thread.Sleep(2000);

            // run synchronously
            taskOuter.RunSynchronously();

            // or schedule
            // taskOuter.Start(TaskScheduler.Defaut);
        });

        taskInner.Wait();
        Console.WriteLine("Enter to exit");
        Console.ReadLine();
    }
}