Почему ожидание холодной задачи не бросает

Я просто экспериментировал, чтобы увидеть, что произойдет, когда ждет холодная задача (т.е. 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() или Result в задаче, возвращаемой Task.Run, вызвал исключение в потоке основной консоли.

Ответ 1

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

System.InvalidOperationException: запуск не может быть вызван задачей с обещанием.

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

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 - эмуляция реальной работы, выполняемой в другой задаче.

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

Ответ 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();
    }
}