17

A continuation of a question asked here :

In the aforementioned question I have the following function which returns an object of type Task (for incremental testing purposes) :

private static Task<object> GetInstance( ) {
    return new Task<object>( (Func<Task<object>>)(async ( ) => {
        await SimpleMessage.ShowAsync( "TEST" );
        return new object( );
    } ) );
}

When I call await GetInstance( );, the function is called (and I assume the task is returned since no exception is thrown) but then the task just sits there.

I can only guess I am doing this wrong then.

I do not want this function to return a task that is already running ( that is IMPERATIVE ).

How do I asynchronously run the task returned by this function?

Community
  • 1
  • 1
Will
  • 3,413
  • 7
  • 50
  • 107
  • 1
    You're `await`ing on `GetInstance`, and that's why your method waits there. It's still an async call, as the thread can be released for that wait. I think you're mixing asynchronous execution with multitasking. – MarcinJuraszek Dec 07 '15 at 23:47
  • Unclear what you are looking for, but perhaps [Asynchronous Lazy Initialization](http://blog.stephencleary.com/2012/08/asynchronous-lazy-initialization.html)? – chue x Dec 08 '15 at 00:18
  • I don't quite understand why this is necessary, if you don't actually want to start getting the instance, why are you calling `GetInstance()` at that point in time? Perhaps you want something like `Func>` or `Lazy>` so you may defer the call to `GetInstance()`. – Lukazoid Dec 08 '15 at 00:18
  • I need to hang onto the object without running it because the task needs to be one which can be cancelled. I need to monitor the running state to determine if it should be started, cancelled, restarted, or if I need to yell at the user for being stupid... – Will Dec 08 '15 at 00:19
  • It is expected for asynchronous methods to return a `Task` which will complete at some point in time without being explicitly started. I would maintain `Func` instances to create the tasks as and when required. – Lukazoid Dec 08 '15 at 00:24

4 Answers4

14

To create a Task already started

Try creating the task like this:

Task.Factory.StartNew<object>((Func<Task<object>>) ...);

To create a Task without starting it

If you don't want the task started, just use new Task<object>(...) as you were using, but then you need to call Start() method on that task before awaiting it!

[Reference]

My recommendation

Just make a method that returns the anonymous function, like this:

private static Func<object> GetFunction( ) {
    return (Func<object>)(( ) => {
        SimpleMessage.Show( "TEST" );
        return new object( );
    } );
}

Then get it and run it in a new Task whenever you need it (Also notice that I removed the async/await from the lambda expression, since you are putting it into a Task already):

Task.Factory.StartNew<object>(GetFunction());

One advantage to this is that you can also call it without putting it into a Task:

GetFunction()();
Joanvo
  • 5,677
  • 2
  • 25
  • 35
  • Wait - Won't that START the task? I need to return the task without it starting, and then start it again. I thought I was pretty specific about that (If it doesn't start the task then it's fine but this looks like it returns a task that's already running to me...) – Will Dec 08 '15 at 00:01
  • Of course, I assumed that was what you wanted because you were awaiting it. Otherwise you should call Task.Start()? – Joanvo Dec 08 '15 at 20:11
  • I modified my answer, hope it helps – Joanvo Dec 08 '15 at 20:21
  • 2
    Given the complexity of the situation with which I am dealing this question is out of the scope of consideration (TL;DR : I was doing it wrong). However your answer is the correct approach for creating a task and returning it without starting it. – Will Dec 08 '15 at 22:18
8

You should never use the Task constructor (or Task.Start).

I do not want this function to return a task that is already running ( that is IMPERATIVE ).

So, you want to return some code that won't execute until you call it? That's a delegate, not a Task.

private static Func<Task<object>> GetInstance()
{
  return async () =>
  {
    await SimpleMessage.ShowAsync("TEST");
    return new object();
  };
}

When you're ready to execute it, call it just like any other delegate. Note that it returns a Task, so you can await the result:

var func = GetInstance();
// Delegate has not started executing yet
var task = func();
// Delegate has started executing
var result = await task;
// Delegate is done
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thank you - but there is more to this than what you think. I need the task because I need it to support task cancellation and restarting. I did not want to bog down the question with what I felt to be details not pertinent to the immediate question. – Will Dec 08 '15 at 01:39
  • @Will: This can work fine with cancellation. What exactly do you mean by "restarting"? – Stephen Cleary Dec 08 '15 at 01:41
  • 3
    Why should one never use Task constructor or Start? – Incerteza Dec 08 '15 at 01:42
  • 1
    @アレックス: I explain in full on my blog ([constructor](http://blog.stephencleary.com/2014/05/a-tour-of-task-part-1-constructors.html) and [`Start`](http://blog.stephencleary.com/2015/02/a-tour-of-task-part-8-starting.html)). – Stephen Cleary Dec 08 '15 at 01:43
  • @StephenCleary : I mean that there is a long-running task in play which executes functions in the background. The task should be one that can be cancelled and started again with relative impunity. Using a Task seemed like the natural choice (given the presence of the CancellationTokenSource class (or whatever it's called I'm too tired to look it up...). – Will Dec 08 '15 at 01:44
  • Saw your edit : I like that. It looks good... Once I get out of the spiders web I'll have to try it... – Will Dec 08 '15 at 01:45
  • @Will: `CancellationToken` works fine with any kind of code. Restarting tasks is not possible; tasks have to be recreated. If you take the `Func` approach, you can create a replacement task by just calling the delegate again. – Stephen Cleary Dec 08 '15 at 01:50
  • @StephenCleary : I know that. The problem into which I was running (again; a whole lot of stuff going on beyond what I posted) was that if the user acted... well, like a USER, you would end up with multiple tasks running right next to each other. Train wreck. When I say "Restart" I mean exactly what you say - get a new task and run it... My solution to the "Train Wreck" is to obtain the task and monitor it. I have to monitor the status when the button responsible for launching (and cancelling) it is pressed because cancelling the task isn't instantaneous. The task runs until it hits CT.Throw – Will Dec 08 '15 at 01:55
  • @StephenCleary (Continued) : and as users can just hammer away at the button you could end up with (like I said) a train wreck in which multiple tasks are running towards cancellation at the same time. Rather than just let the user mash the button it has to watch the status of the task. Is it just created? Good : run it. No? Has cancel been called on the cancellation token? No? Fine : Cancel it. Has that happened? Okay : Scream at the user to stop being stupid; once the task has been cancelled (correctly) then the CTS will be disposed of and restored so that the process can start again. – Will Dec 08 '15 at 02:00
  • @Will: Just use a `Lazy` then, with a `CTS.IsCancellationRequested` check. – Stephen Cleary Dec 08 '15 at 03:02
  • I've already gotten where I need to be (unless what you are suggesting would resolve my *new* problem here : http://stackoverflow.com/questions/34147144/why-does-new-taskobject-functaskobject-async-work-when-ne ); Thanks. – Will Dec 08 '15 at 03:04
4

You are stuck with a bad design there. I'll try to make something work for you under these constraints.

The only way to delay start tasks is to use the Task.Start method. (You also can use RunSynchronously but that doesn't really make use of any of the tasks features. At this point the task becomes an unthreaded lazy object.

So use the Task.Start method.

await does not start tasks. It waits for tasks that already run. Therefore await new Task(() => { }) always freezes forever.

Another problem here:

return new Task<object>( (Func<Task<object>>)(async ( ) => {
    await SimpleMessage.ShowAsync( "TEST" );
    return new object( );
} ) );

When you start that task it will complete nearly instantly and Task.Result will hold another task - the one returned by the async lambda. I doubt this is what you want. Hard to make this work and I don't know what you need.

This smells like the XY problem. Can you elaborate on what you want to accomplish? It feels like you have given yourself unnecessary constraints.

usr
  • 168,620
  • 35
  • 240
  • 369
  • I think it would be better taken into chat - No need to transcribe war and peace here... now I just need to figure out how to start a chat session without being prompted to do so... – Will Dec 08 '15 at 00:17
  • *DING* There's been a development... https://chat.stackoverflow.com/rooms/97243/await-new-taskt – Will Dec 08 '15 at 02:26
  • Please see http://stackoverflow.com/questions/34147144/why-does-new-taskobject-functaskobject-async-work-when-ne – Will Dec 08 '15 at 03:01
0

OK, I think I've figured this out. Please examine this code:

class Program
{
    public static void Main(string[] args)
    {
        // Only RUN the task as needed.  FooGet 
        // still allows you to generalize your task.
        Task.Run(() =>
        {
            dynamic value = FooGet();

            value.RunSynchronously();

            Console.WriteLine(value.Result.Result.ToString());
        });

        while (true) Thread.Sleep(100);
    }

    private static Task<object> FooGet()
    {
        var task = new Task<object>(() => {
            return asyncBar();
        });

        return task;
    }

    private async static Task<object> asyncBar()
    {
        // do work!
        return "Hello, world!";
    }
}
The Sharp Ninja
  • 1,041
  • 9
  • 18