5

I have a variable number of tasks defined in Func<Task>types so I can define the tasks but not have them automatically start. Say, for example:

Func<Task> task1Func = () => DoTask1Async();

Some of the tasks defined in such a way return values. I need a way to 'pass' the result of such tasks to arguments of subsequent tasks, defined in the same way. So, I would like to write:

Func<Task> task2Func = () => DoTask2Async(task1Func.Invoke().Result));

The trouble with this is that it the task1Func.Invoke() part starts the task1 task again which is no good.

I can't reframe the input to chain continuations like:

Func<Task> funcTasks = () => DoTask1Async().ContinueWith(t => DoTask2Async(t.Result));

as I need to know in code the point at which each task (i.e. DoTask1Async / DoTask2Async) in the chain completes to run some other code. Another problem with this is that I don't know a way to compute how many tasks are involved in the 'chain'.

I can't store the task results as they complete as the tasks need to be defined declaratively like task1Func and task2Func.

The tasks are processed in a defined order, with each task completing before the next is processed. Results from tasks are used for subsequent tasks only.

Edit

In response to request for runnable code using task1Func.Invoke().Result here it is. Create a new WPF project and add a button to the default Grid. Then clear the MainWindow.xaml.cs file and paste in what's below. This is the closest code to my real project, while cutting out all of the things irrelevant to the issue.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;

namespace WpfApplication2
{

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        // Declare first task that returns a result
        Func<Task<int>> deferredTask1 = () => DoTask1Async();
        // Declare second task using result of first.
        // BUT this isn't good as it calls deferredTask1 AGAIN, so need another approach.
        Func<Task> deferredTask2 = () => DoTask2Async(deferredTask1.Invoke().Result);

        var item1 = new MyItem() { MyDeferredTask = deferredTask1 };
        var item2 = new MyItem() { MyDeferredTask = deferredTask2 };

        List<MyItem> items = new List<MyItem>();

        items.Add(item1);
        items.Add(item2);

        await DoTasksAsync(items);

    }

    public static async Task<int> DoTask1Async()
    {
        return await Task<int>.Run(() => { return 3000; });
    }

    public static async Task DoTask2Async(int delay)
    {
        await Task.Delay(delay);
    }

    private async Task DoTasksAsync(IEnumerable<MyItem> items)
    {
        foreach (var item in items)
        {
            await item.MyDeferredTask();
        }
    }

    private class MyItem
    {
        public Func<Task> MyDeferredTask { get; set; }
    }
}
}
halfer
  • 19,824
  • 17
  • 99
  • 186
Cleve
  • 1,273
  • 1
  • 13
  • 26
  • Can you change the signature from `Func` to `Func`? – Scott Chamberlain Oct 26 '16 at 15:09
  • @ScottChamberlain, yes that's possible. – Cleve Oct 26 '16 at 15:11
  • I think we need a fuller example of how you would setup multiple tasks and link them. Create a [runnable example](http://stackoverflow.com/help/mcve) for us using the `task1Func.Invoke().Result` version in the example. It will help us greatly in showing us how to do it a better way. – Scott Chamberlain Oct 26 '16 at 15:16
  • @ScottChamberlain , please see my edit above in response to your request. Hope this helps. – Cleve Oct 27 '16 at 09:53
  • Are you sure you're coming at this from the right direction? Might something like [Dataflow](https://msdn.microsoft.com/en-us/library/hh228603(v=vs.110).aspx) be a better starting point for this? – Damien_The_Unbeliever Oct 27 '16 at 12:53
  • @Damien_The_Unbeliever - You might well be right! Are you able to offer some example code that would tackle my issues? – Cleve Oct 27 '16 at 12:59
  • This looks way more complicated than it should be...What can't you just 'await' for the tasks one after the other? You did that in your last method eventually anyway. – Miklós Tóth Oct 30 '16 at 15:06
  • @MiklósTóth, the declaration of the tasks and there eventual call to be executed don't happen in the same method or at the same time, although the example code might inadvertently suggest this. So, I need a way to declare the tasks in advance while at the same time being able to use their results in subsequent tasks, but starting the tasks off at some future point. That is why can't simply await them. – Cleve Oct 30 '16 at 15:55
  • What if the second method (DoTask2Async) receives a Task parameter instead of an int? That way the method can await the task (which would be the first task) inside and perform other stuff after. – Miklós Tóth Oct 30 '16 at 16:14
  • @MiklósTóth , thanks for your comments. Sadly, not all of the Async methods that need to be called are editable by me, so it won't be possible to change the method signature. Also, passing in a task would start it, which is not what I want to happen. – Cleve Oct 30 '16 at 17:08

1 Answers1

0

Using Microsoft's Reactive Framework you can do this:

var query =
    from r1 in Observable.FromAsync(() => DoTask1Async())
    from r2 in Observable.FromAsync(() => DoTask2Async(r1))
    select new { r1, r2 };

await query;
Enigmativity
  • 113,464
  • 11
  • 89
  • 172