72

I want to use this Task<TResult> constructor. I can't seem to get the syntax right. Could someone correct my code?

Also, am I right thinking that if a Task is constructed that way, it's not started?

The constructor I think I need is:

Task<TResult>(Func<Object, TResult>, Object)

The error I get is:

Argument 1: cannot convert from 'method group' to 'System.Func<object,int>'

static void Main(string[] args)
{
    var t = new Task<int>(GetIntAsync, "3"); // error is on this line
    // ...
}

static async Task<int> GetIntAsync(string callerThreadId)
{
    // ...
    return someInt;
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
G. Stoynev
  • 7,389
  • 6
  • 38
  • 49
  • I'm confused, your method is already `async`, why do you want to enclose it in another `Task`? – svick Apr 17 '13 at 19:26
  • :-) to model something you actually commented on [this](http://stackoverflow.com/questions/16054467/does-task-runsynchronously-work-recursively) thread – G. Stoynev Apr 17 '13 at 19:33
  • Well, this way you're going to get an unstarted `Task`, but it's not the same `Task` that would be returned from the `async` method. – svick Apr 17 '13 at 19:52
  • 1
    It's a Task wrapped around it? – G. Stoynev Apr 17 '13 at 20:08
  • Yeah, it's a code-based `Task` that will invoke the promise `Task` when started. – svick Apr 17 '13 at 20:49
  • @svick one reason to want to do this is that you may want to postpone the execution of the first leg of the async method. With direct await foobar().ConfigureAwait(bool) you get the code within foobar to run synchronously until the first await contained there-in. If instead you wrap this in a Task, you wrap the first led in the task so you can construct the call before starting it. Hence have the opportunity to touch aspects affecting the async state machine continuation, such as the thread's current context. – David Burg Jul 27 '21 at 22:38

6 Answers6

52
var t = new Task<int>(() => GetIntAsync("3").Result);

Or

var t = new Task<int>((ob) => GetIntAsync((string) ob).Result, "3");

To avoid using lambda, you need to write a static method like this:

private static int GetInt(object state)
{
   return GetIntAsync(((string) state)).Result;
}

And then:

var t = new Task<int>(GetInt, "3");
Vyacheslav Volkov
  • 4,592
  • 1
  • 20
  • 20
  • Aren't you using a different constructor, than the one I asked about? – G. Stoynev Apr 17 '13 at 17:53
  • The first constructor uses a closure to pass parameters, the second constructor uses a state to pass parameters. – Vyacheslav Volkov Apr 17 '13 at 18:03
  • 2
    You're still closing over `this` in the second case, so you're only using state to pass one of the two variables. – Servy Apr 17 '13 at 18:10
  • 1
    You're right closing exist, but `this` is not in use, because the GetIntAsync method is static. – Vyacheslav Volkov Apr 17 '13 at 18:20
  • 1
    Not to be a pest, but can you show syntax that doesn't use lambdas – G. Stoynev Apr 17 '13 at 18:40
  • 6
    Not sure how this corresponds to the Q, using Task.Result will block, which does not seem right, and how is this related to creating but not starting the Task? Can you not use an async lambda and then await the result ? – Martin Meeser Aug 19 '19 at 10:11
  • 4
    This approach is using async to sync transition, which is dangerous (deadlock risk and the like). Theodor's approach is hence better. – David Burg Jul 27 '21 at 22:35
9

To use the Task constructor that accepts an object state argument you must have a function that accepts an object argument too. Generally this is not convenient. The reason that this constructor exists is for avoiding the allocation of an object (a closure) in hot paths. For normal usage the overhead of closures is negligible, and avoiding them will complicate your code for no reason. So this is the constructor you should use instead:

public Task (Func<TResult> function);

...with this lambda as argument:

() => GetIntAsync("3")

There is one peculiarity in your case though: the lambda you pass to the constructor returns a Task<int>. This means that the generic type TResult is resolved to Task<int>, and so you end up with a nested task:

var t = new Task<Task<int>>(() => GetIntAsync("3"));

Starting the outer task will result to the creation of the inner task. To get the final result you'll have to use the await operator twice, one for the completion of the outer task, and one for the completion of the inner task:

static async Task Main(string[] args)
{
    var outerTask = new Task<Task<int>>(() => GetIntAsync("3"));

    //...

    outerTask.Start(TaskScheduler.Default); // Run on the ThreadPool
    //outerTask.RunSynchronously(TaskScheduler.Default) // Run on the current thread

    //...

    Task<int> innerTask = await outerTask; // At this point the inner task has been created
    int result = await innerTask; // At this point the inner task has been completed
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
2

I find that an async lambda works best, because:

  1. the work is started by calling the method which means you don't need to call .Start, so it's not possible to forget to do it.
  2. this approach doesn't 'suffer' from the inner/outer task problem; you always await the right task, not it's wrapper.
Func<Task<int>> f = async () => await WorkAsync(2);

Console.WriteLine("Sleeping before start");

await Task.Delay(100);

Console.WriteLine("Waking up to start");

var result = await f();

Console.WriteLine($"The work is done and the answer is: {result}");

This results in the following output:

Sleeping before start
Waking up to start
Starting 2
Ending 2
The work is done and the answer is: 4

tymtam
  • 31,798
  • 8
  • 86
  • 126
  • aren't you stuck with the problem that you don't have access to that task? You only have access to its result, but you can't query the value of Task.IsFaulted, Task.Exception etc. The moment that stupid lambda function is called, the task is started. I want to actually CREATE the task, using the lambda function, and THEN launch it via await, so that I can ACCESS the task =) it's frustrating :D – Daveloper Apr 16 '21 at 06:16
  • @Daveloper I'm not sure I follow. What cannot you achieve with a `Func`, because 1. It can be started when you want it; 2. `f()` gives a handle 3. You can `await` or pass it `WhenAny` etc. 4. You can examine it's state 5. Error handling can be done inside the lamba or in the code that launches the task. – tymtam Apr 16 '21 at 10:24
2

Had a similar issue where we wanted to ensure a task was re-usable without re-starting the task. Rx saved our bacon. You'll need the nuget package System.Reactive.

See below GetIntTask returns a Task that isn't started, and GetIntOnce ensures the task gets started, and the result of the task is buffered and replayed to any callers (with Replay(1))

async Task Main()
{
    var awaitableResult = GetIntOnce();
    Console.WriteLine("Has the task started yet? No!");
    var firstResult = await awaitableResult;
    Console.WriteLine($"Awaited Once: {firstResult}");
    var secondResult = await awaitableResult;
    Console.WriteLine($"Awaited Twice: {secondResult}");
}

public static IObservable<int> GetIntOnce()
{
    return Observable.FromAsync(() =>
        {
            var t = GetIntTask();
            t.Start();
            return t;
        }).Replay(1).AutoConnect();
}

public static Task<int> GetIntTask()
{
    var t = new Task<int>(
    () =>
    {
        Console.WriteLine("Task is running!");
        return 10;
    });
    return t;
}

The output is:

Has the task started yet? No!
Task is running!
Awaited Once: 10
Awaited Twice: 10
Flatliner DOA
  • 6,128
  • 4
  • 30
  • 39
  • Above author is my work colleague, who helped me figure this out with Rx. After much pain, I realized that the fundamental design of Task is wrong. – Shailen Sukul Aug 17 '21 at 01:57
  • While this works, it seems to me a quite convoluted way of doing things. With this approach you also lose the facilities of the `Task` class, like the `IsCompleted`, `IsFaulted` and `Status` properties, the ability to await multiple operations at the same time `await Task.WhenAll()`, and more. I would suggest to take a look at this question: [Enforce an async method to be called once](https://stackoverflow.com/questions/28340177/enforce-an-async-method-to-be-called-once/) – Theodor Zoulias Aug 17 '21 at 13:03
-3

A new approach in .net 5 (Actually I have no idea when you can start doing this, I'm using .net 5 now)

Task<int> MyStartLaterTask = null;
//... Some Time Later
MyStartLaterTask ??= GetIntAsync(string callerThreadId);
await MyStartLaterTask;

I appreciate that this isn't exactly "define upfront and use later" but as far as I can see this does the same thing. I have a loop and I wanted to define a task that was only run when needed and only run once. This does just that, it creates a task that I can assign when it's first needed and then await it whenever its output is required.

Tod
  • 2,070
  • 21
  • 27
-6
   //creating task
   var yourTask = Task<int>.Factory.StartNew(() => GetIntAsync("3").Result);

   //...

   int result = yourTask.Result;

UPDATE:

Yes, unfortunately it does start the task. Use code as mentioned above instead:

   //creating task
   var yourTask = new Task<int>(() => GetIntAsync("3").Result);

   //...

   // call task when you want
   int result = yourTask.Start();
Greg
  • 126
  • 6
  • 5
    This is creating **and starting**. The task. He wants to create it **without** starting it. – Servy Apr 17 '13 at 17:42