Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I create a Task that uses await inside the body that behaves the same as the synchronous version when Wait is called?

I have some code that creates a task that does some slow work like this:

public static Task wait1()
{
    return new Task(() =>
    {
        Console.WriteLine("Waiting...");
        Thread.Sleep(10000);
        Console.WriteLine("Done!");
    });
}

In the real implementation, the Thread.Sleep will actually be a web service call. I would like to change the body of the method can use await (so it does not consume a thread during the network access/sleep). My first attempt (based on shotgun-debugging the compile errors) was this:

public static Task wait2()
{
    return new Task(async () =>
    {
        Console.WriteLine("Waiting...");
        await Task.Delay(10000);
        Console.WriteLine("Done!");
    });
}

However; this task doesn't seem to behave the same as the first one, because when I call .Wait() on it; it returns immediately.

Below is a full sample (console app) showing the differences (the app will end immediately when the second task starts).

What do I need to do so that I can call Start and Wait on a Task which happens to have code using await inside it? The tasks are queued and executed later by an agent, so it's vital that the task is not auto-started.

class Program
{
    static void Main(string[] args)
    {
        var w1 = wait1();
        w1.Start();
        w1.Wait(); // This waits 110 seconds

        var w2 = wait2();
        w2.Start();
        w2.Wait(); // This returns immediately
    }

    public static Task wait1()
    {
        return new Task(() =>
        {
            Console.WriteLine("Waiting...");
            Thread.Sleep(10000);
            Console.WriteLine("Done!");
        });
    }

    public static Task wait2()
    {
        return new Task(async () =>
        {
            Console.WriteLine("Waiting...");
            await Task.Delay(10000);
            Console.WriteLine("Done!");
        });
    }
}
like image 783
Danny Tuppeny Avatar asked Jul 07 '13 17:07

Danny Tuppeny


2 Answers

It seems like this isn't possible! See alexm's answer here:

Tasks returned by async methods are always hot i.e. they are created in Running state.

:-(

I've worked around this by making my agent queue Func<Task>s instead, and the overload that receives a task simply queues () => task. Then; when de-queing a task, I check if it's not running, and if so, start it:

var currentTask = currentTaskFunction();
if (currentTask.Status == TaskStatus.Created)
    currentTask.Start();

It seems a little clunky to have to do this (if this simple workaround works; why the original restriction on async methods always being created hot?), but it seems to work for me :-)

like image 70
Danny Tuppeny Avatar answered Sep 28 '22 08:09

Danny Tuppeny


You could write this as:

public static async Task Wait2()
{
    Console.WriteLine("Waiting...");
    await Task.Delay(10000);
    Console.WriteLine("Done!");
}

In general, it's rarely a good idea to ever use new Task or new Task<T>. If you must launch a task using the ThreadPool instead of using the async/await language support to compose one, you should use Task.Run to start the task. This will schedule the task to run (which is important, tasks should always be "hot" by conventions).

Note that doing this will make it so you don't have to call Task.Start, as well.

like image 31
Reed Copsey Avatar answered Sep 28 '22 08:09

Reed Copsey