使用不同返回类型的任务链接

3

在IT技术中,是否有可能链接不同返回类型或者没有返回类型的任务呢?例如,在伪代码中:

Task<double>.ContinueWith(Task<string>).ContinueWith(Task<String>).ContinueWith(Task)

以下是一个真实代码示例:

private double SumRootN(int root)
{
    double result = 0;
    for (int i = 1; i < 10000000; i++)
    {
        result += Math.Exp(Math.Log(i) / root);
    }
    return result;
}

private void taskSequentialContinuationButton_Click(object sender, RoutedEventArgs e)
{
    Task<double> task = null;

    this.statusText.Text = "";
    this.watch = Stopwatch.StartNew();
    for (int i = 2; i < 20; i++)
    {
        int j = i;
        if (task == null)
        {
            task = Task<double>.Factory.StartNew(() => { return SumRootN(j); });
        }
        else
        {
            task = task.ContinueWith((t) => { return SumRootN(j); });
        }

        task = task.ContinueWith((t) => 
        { // I don't want to return anything from this task but I have to, to get it to compile
            this.statusText.Text += String.Format("root {0} : {1}\n", j, t.Result);
            return t.Result;
        }, TaskScheduler.FromCurrentSynchronizationContext());
    }

    task.ContinueWith((t) => 
        { // I also don't want to return anything here but I don't seem to have to here even though intellisense expects a Task<double>??
            this.statusText.Text += String.Format("{0}ms elapsed\n", watch.ElapsedMilliseconds);
        }, TaskScheduler.FromCurrentSynchronizationContext());
}

请参考内联注释中的奇怪之处来了解链式调用的细节。

你为什么要在序列中使用这么多的“Task”?难道你不能在单个“Task”中运行循环,从而创建“SynchronizationContext”“Task”吗? - svick
我认为这里的代码示例可能会带来更多的负面影响。您能否解释一下您对运行时行为的期望?因为现有的代码似乎会“丢弃”除最后一个SumRootN调用之外的所有结果,因为您的ContinueWith调用没有使用“t”或“t.Result”。 - James Manning
就我所知,基于你想要做的事情,你也可以考虑使用Parallel.For - http://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel.for.aspx - James Manning
这只是一个示例的丢弃代码,不会在应用程序中使用。我正在尝试创建一个按顺序执行的任务链,其中更新UI的继续任务在UI线程中运行。更新UI的任务不需要将值传递给下一个任务,但如果它们不这样做,代码将无法编译。因此标题为“使用不同返回类型链接任务” - 是否可能? - flolim
@JamesManning 这里的计算根本不是并行的。我不确定是否希望将其并行化,特别是因为这意味着结果将以错误的顺序显示。 - svick
显示剩余2条评论
2个回答

4

如果您想链接具有不同返回类型的任务,只需将它们放在不同的变量中:

Task<Type1> task1 = Task.Factory.StartNew(() => Compute1());
Task<Type2> task2 = task1.ContinueWith(_ => Compute2());
Task<Type3> task3 = task2.ContinueWith(_ => Compute3());

对于你所述的情况,如果你在循环中计算某些东西并希望在每次迭代后在UI线程上报告,可以按照以下方式完成:

var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

Task.Factory.StartNew(() =>
{
    for (int i = 2; i < 20; i++)
    {
        // perform computation

        Task.Factory.StartNew(() =>
        {
            // report result
        }, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
    }
});

如果您想要在循环中报告进度,使用BackgroundWorker可能会更简单。FWIW - James Manning
如果这确实是你想做的全部,那么使用“任务”就可以了。但是,使用“任务”意味着如果你想要更改任何内容,那么很可能会更容易。 - svick

0

您的代码由于重复使用单个task变量而变得复杂。看起来您想启动多个任务并等待它们完成。类似这样的内容应该可以解决:

SynchronizationContext context = SynchronizationContext.Current;

Task[] tasks = Enumerable.Range(2, 19)
    .Select(d => Task<double>.Factory.StartNew(() => SumRootN(d))
        .ContinueWith(t => {
            this.statusText.Text += string.Format("root {0} : {1}\n", d, t.Result);
        }))
    .ToArray();

Task.Factory.StartNew(() => {
    Task.WaitForAll(tasks);
    string msg = string.Format("{0}ms elapsed\n", watch.ElapsedMilliseconds);
    context.Post(_ => { this.statusText.Text += msg; }, null);
});

编辑:如果你想创建一系列的任务,可以尝试这个方法:

Task first = new Task(() => { });
Task outer = Enumerable.Range(2, 19)
    .Aggregate(first, (task, d) => {
        Task inner = new Task<double>(() => SumRootN(d))
            .ContinueWith(rt => {
                this.statusText.Text += String.Format("root {0} : {1}\n", d, rt.Result);
            });
        return task.ContinueWith(inner);
    });

outer.ContinueWith(t => {
    this.statusText.Text += String.Format("{0}ms elapsed\n", watch.ElapsedMilliseconds);
});

first.Start();

1
这将在代码执行时冻结用户界面。您真的不应该在“同步上下文”上使用Wait()(或WaitForAll())。 - svick
那段代码肯定更简单,但我正在尝试将任务链接在一起,以便它们在后台按顺序执行。我的意思是做SumRootN(2) -> 在UI中显示结果 -> SumRootN(3) -> 在UI中显示结果 -> ... -> 显示总时间。我知道并行处理速度要快得多,但实际上我只是想举一个例子,说明如何连接一个顺序链(可能每个链中的“链接”都有不同的返回类型)。 - flolim
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx - James Manning
具体而言,如果您只有一个串行任务,并且只想报告进度和完成情况,那么使用单个BackgroundWorker实例(它自动处理在UI线程上调用ShowProgress和completed方法)而不是Tasks,您的代码将更简单。 - James Manning

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接