25

My code:

var r = from x in new Task<int>(() => 1)
        from y in new Task<int>(() => x + 1) 
        select y;
r.ContinueWith(x => Console.WriteLine(x.Result)).Start();   

or

new Task<int>(() => 1)
    .ContinueWith(x => x.Result + 1)
    .ContinueWith(x => Console.WriteLine(x.Result))
    .Start();

Exception:

Start may not be called on a continuation task.

So I need to start the first task. Is there any way to call last task Start method to run all tasks?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
dotneter
  • 1,689
  • 2
  • 15
  • 24
  • 2
    I still don't think this question has an answer, really. I am working on a solution for this problem, too. To emphasize something you wrote: _I just want to compose tasks. Decision about running it or not can be anywhere_ – Thomas May 23 '16 at 20:05

6 Answers6

8

Any reason not to use Task.Factory.StartNewmsdn, ms docs for the first task? Yes, it's inconsistent - but it's fundamentally a different kind of task, in terms of being started explicitly rather than just as a continuation.

Bakudan
  • 19,134
  • 9
  • 53
  • 73
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 6
    I just want to compose tasks. Decision about running it or not can be anywhere. – dotneter Dec 10 '10 at 13:33
  • 1
    @dotneter: Well another option is to create the initial task outside the query expression, then use it in the query expression, then start it. – Jon Skeet Dec 10 '10 at 13:38
  • But if I want return my complex task from method I need to deal with two tasks, root for start and last one for composing with ContinueWith – dotneter Dec 10 '10 at 13:44
  • @dotneter: So you want a task you can start, but with a result of the end of the query? Not sure the best way to do that, but I think I see what you mean... – Jon Skeet Dec 10 '10 at 13:46
  • Yes, I think it's similar to standard linq scenario. – dotneter Dec 10 '10 at 13:50
4

I'm not really sure what's wrong with just writing this:

var t1 = new Task<int>(() => 1)
var r = from x in t1
        from y in new Task<int>(() => x + 1) 
        select y;
r.ContinueWith(x => Console.WriteLine(x.Result));
t1.Start();

or this:

var t = new Task<int>(() => 1)
t.ContinueWith(x => x.Result + 1)
 .ContinueWith(x => Console.WriteLine(x.Result))
t.Start();

That directly expresses what you actually want to do. (It's the initial task that you want to kick off. So what's wrong with invoking Start on that initial task?) Why are you looking for a syntax that obscures that?

EDIT: fixed first example...

EDIT 2 to add:

So I realise now that LinqToTasks expects task selectors to return running tasks. So the second from clause in your first example returns a task that nothing will ever run. So what you actually need is this:

var t1 = new Task<int>(() => 1);
var r = from x in t1
        from y in Task<int>.Factory.StartNew(() => x + 1)
        select y;
r.ContinueWith(x => Console.WriteLine(x.Result));
t1.Start();

Nothing else is going to call Start on the tasks produced in these from clauses. Since the relevant selectors don't actually get executed until the previous task completes, you're still in control of when to kick off the root task.

That appears to work, but it's pretty ugly. But that appears to be how LinqToTasks is designed... I think I'd stick with the regular function call syntax.

Ian Griffiths
  • 14,302
  • 2
  • 64
  • 88
  • 1
    First one, throw exception Start may not be called on a task with null action. Second one is ok, but if I want return my complex task from method I need to deal with two tasks, root for start and last one for composing with ContinueWith. – dotneter Dec 10 '10 at 15:06
  • 1
    Yes sorry - I realised immediately after I posted it that my first example was wrong, so I updated it to put the initial task in a separate variable. – Ian Griffiths Dec 10 '10 at 16:02
0

I had the same problem today. I wanted to create a wrapper task that handles an error from an inner task. This is what I came up with:

var yourInitialTask = new Task(delegate
{
    throw e;
});

var continuation = task.ContinueWith(t =>
{
    if (task.IsCanceled)
    {
        Debug.WriteLine("IsCanceled: " + job.GetType());
    }
    else if (task.IsFaulted)
    {
        Debug.WriteLine("IsFaulted: " + job.GetType());
    }
    else if (task.IsCompleted)
    {
        Debug.WriteLine("IsCompleted: " + job.GetType());
    }
}, TaskContinuationOptions.ExecuteSynchronously); //or consider removing execute synchronously if your continuation task is going to take long

var wrapper = new Task(() =>
{
   task.Start();
   continuation.Wait();
});

return wrapper;

The key features here are that -the continuation part runs after the original task, as you want -the wrapper is Startable. Continuation tasks created with ContineWith() are nto Startable.

A less key feature of this example, is that the exception is being logged and discarded (solves my problem, not yours). You might want to be doing something different when exceptions occur in the continuation such as rethrow it as an exception of the current task, so that it bubbles out.

Tim Lovell-Smith
  • 15,310
  • 14
  • 76
  • 93
0

As far as I'm aware, there's no sensible way to compose non-started tasks provided by the framework. The simplest solution I can think of is extension methods. Here are some examples which you could build on if you need this functionality.

Warning: Just as with passing around and composing tons of lambdas, if you find yourself needing these, it often means you are missing a type in your design that would simplify your code. Ask yourself what you gained by creating the subtasks.

/// <summary>
/// Compose tasks without starting them.
/// Waiting on the returned task waits for both components to complete.
/// An exception in the first task will stop the second task running.
/// </summary>
public static class TaskExtensions
{
    public static Task FollowedBy(this Task first, Task second)
    {
        return FollowedBy(first,
            () =>
            {
                second.Start();
                second.Wait();
            });
    }

    public static Task FollowedBy(this Task first, Action second)
    {
        return new Task(
            () =>
            {
                if (first.Status == TaskStatus.Created) first.Start();
                first.Wait();
                second();
            });
    }

    public static Task FollowedBy<T>(this Task first, Task<T> second)
    {
        return new Task<T>(
            () =>
            {
                if (first.Status == TaskStatus.Created) first.Start();
                first.Wait();
                second.Start();
                return second.Result;
            });
    }

    public static Task FollowedBy<T>(this Task<T> first, Action<T> second)
    {
        return new Task(
            () =>
            {
                if (first.Status == TaskStatus.Created) first.Start();
                var firstResult = first.Result;
                second(firstResult);
            });
    }

    public static Task<TSecond> FollowedBy<TFirst, TSecond>(this Task<TFirst> first, Func<TFirst, TSecond> second)
    {
        return new Task<TSecond>(
            () =>
            {
                if (first.Status == TaskStatus.Created) first.Start();
                return second(first.Result);
            });
    }
}
Graham
  • 1,529
  • 16
  • 22
0

The answer is simple. ContinueWith is automatically start task. And first task need to be running.

var r= Task<int>.Run<int>( () => 1 )
             .ContinueWith<int>( x => x.Result + 1 )
             .ContinueWith( x => Console.WriteLine( x.Result ) );

ContinueWith return task that start with checking previous task is done or not. This code work in the same way like below code

 var firstTask = new Task<int>( () => 1 );
        firstTask.Start();
        var firstawaiter = firstTask.GetAwaiter();
        var secondTask = new Task<int>( x => (int)x + 1 , firstawaiter.GetResult());

        firstawaiter.OnCompleted( () =>
        {
            secondTask.Start();
        } );

        var secondawaiter = secondTask.GetAwaiter();


        var thirdTask = new Task( x => Console.WriteLine( x ) , secondawaiter.GetResult());

        secondawaiter.OnCompleted( () =>
        {
            thirdTask.Start();
        } );

So, If first task is not completed, next task will not be started.

And you not need to start continuewith block .

JaeWoo So
  • 568
  • 5
  • 18
0

The problem is that selecting tasks with LINQ Will Only Create an Expression Tree!

So here's what you need to do:

var query = 
    from i in Enumerable.Range(1, 4) 
    let task = Task.Factory.StartNew(() => Tuple.Create(i, IsPrime(i))) // put a breakpoint here
    select task.ContinueWith(delegate {
        Console.WriteLine("{0} {1} prime.", _.Result.Item1, _.Result.Item2 ? "is" : "is not");
    });
// breakpoint never hit yet
query.ToArray(); // breakpoint hit here 4 times
// all tasks are now running and continuations will start
TaskEx.Await(query.ToArray()); // breakpoint hit 4 more times!!
ewj
  • 21
  • 2