C# 从TPL任务派生以从方法返回更多细节可以吗?

C# 从TPL任务派生以从方法返回更多细节可以吗?,c#,.net,asynchronous,async-await,task-parallel-library,C#,.net,Asynchronous,Async Await,Task Parallel Library,我最初的方法如下所示: string DoSomeWork(); 方法DoSomeWork在另一个线程上启动一些工作,并返回执行ID(只是随机字符串)。稍后,我可以通过返回的执行ID查询结果。主要的一点是在作业完成之前使执行ID可用 现在我想将签名更改为返回任务,以便用户可以等待 Task DoSomeWork(); 同时,我仍然需要返回执行ID(例如用于跟踪目的),我看到了一些选项。首先,使用out参数,第二,返回包含执行ID和任务的元组(在C#中,这看起来不是最好的选择),第三,我实际上

我最初的方法如下所示:

string DoSomeWork();
方法
DoSomeWork
在另一个线程上启动一些工作,并返回执行ID(只是随机字符串)。稍后,我可以通过返回的执行ID查询结果。主要的一点是在作业完成之前使执行ID可用

现在我想将签名更改为返回
任务
,以便用户可以等待

Task DoSomeWork();
同时,我仍然需要返回执行ID(例如用于跟踪目的),我看到了一些选项。首先,使用
out
参数,第二,返回包含执行ID和任务的元组(在C#中,这看起来不是最好的选择),第三,我实际上想问一下

如果我创建一个派生
任务的类

public class ExtendedTask : Task
{
     public string ExecutionID {get; set;}
}
这个看起来可以吗?还是决定其他选择更好

另外,在BCL中有一些类是从
任务
派生的


更新,似乎我没有足够清楚地定义这一点。但是我需要在作业完成之前访问ExecutionID,因此我不能使用
任务。结果

我建议改用
任务
,因为它允许您在任务结果中“嵌入”其他信息

例如,在您的情况下,有如下内容可能是有意义的:

class ExecutionResult
{
     public int ExecutionID { get; set; }
     public string Result { get; set; }
     // ...
}


public Task<ExecutionResult> DoSomeWork()
{
     return Task.Factory.StartNew( () =>
     {
          // Replace with real work, etc...
          return new ExecutionResult { ExecutionID = 0, Result = "Foo" };
     });
}
类执行结果
{
public int ExecutionID{get;set;}
公共字符串结果{get;set;}
// ...
}
公共任务DoSomeWork()
{
返回Task.Factory.StartNew(()=>
{
//用实际工作等代替。。。
返回新的ExecutionResult{ExecutionID=0,Result=“Foo”};
});
}

根据评论进行编辑:

如果您在任务“完成”之前需要数据,并尝试出于其他目的访问此数据,我建议创建一个包含任务和其他数据的类,然后返回它,即:

class ExecutionResult
{
     public int ExecutionID { get; private set; }
     public Task<string> Result { get; private set; }
     // ... Add constructor, etc...
}


public ExecutionResult DoSomeWork()
{
     var task = Task.Factory.StartNew( () =>
     {
          // Replace with real work, etc...
          return "Foo";
     });

     return new ExecutionResult(1, task); // Make the result from the int + Task<string>
}
类执行结果
{
public int ExecutionID{get;private set;}
公共任务结果{get;private set;}
//…添加构造函数等。。。
}
公共执行结果DoSomeWork()
{
var task=task.Factory.StartNew(()=>
{
//用实际工作等代替。。。
返回“Foo”;
});
返回新的ExecutionResult(1,任务);//从int+任务生成结果
}
这仍然允许您访问有关流程的信息,以及
任务
/
任务

我个人不会扩展
任务
,而是编写它。这样,您就不必担心任何只返回
任务
的API—您只需包装任务即可。您可以拥有一个公开底层任务的属性,出于C#5异步目的,您可以在自己的类型上实现waiter模式——但我觉得创建自己的派生类型可能弊大于利。不过,这主要是一种直觉

另一个选择是反过来工作:在属性中存储您的额外状态;毕竟,这就是它的目的。这样,您就可以轻松地传递任务,而不会丢失其逻辑部分的执行上下文。

如果您决定从
任务
任务
继承,您可能会遇到这样的挫折:为任务提供实际工时的
操作
Func
委托必须在构建任务派生对象时指定,并且以后不能更改。即使基类构造函数没有
Start()
新创建的任务,而且实际上它可能要过很长时间才能启动(如果有的话),这也是正确的

这使得在必须在其最终工作的全部细节可用之前创建实例的情况下,很难使用
任务
派生类

一个例子可能是众所周知的
任务
节点的无定形网络,这些节点在一个共享目标上工作,以便它们以特别的方式访问彼此的
结果
属性。要保证您可以在网络中的任意节点上执行
Wait()
,最简单的方法是在启动任何节点之前预先构建所有节点。这巧妙地避免了试图分析工作图依赖关系的问题,并允许运行时因素确定何时、是否以及以何种顺序要求
Result

这里的问题是,对于某些节点,您可能无法提供在构建时定义工作的函数。如果创建必要的lambda函数需要关闭网络中其他任务的
结果
值,则提供我们想要的
结果
任务
可能尚未构建。即使它恰好是在预构建阶段较早构建的,您也不能对其调用
Start()
,因为它可能包含对其他节点的依赖关系,而这些节点没有。记住,预先构建网络的全部目的是避免类似的复杂性

如果这还不够的话,还有其他一些原因,那就是必须使用lambda函数来提供所需的函数是不方便的。因为它是作为参数传递到构造函数中的,所以函数无法访问最终任务实例的
this
指针,这导致了难看的代码,特别是考虑到lambda必须在某个不相关的
this
指针的范围内定义,并且可能是无意中的闭包

我可以继续,但底线是,在派生类中定义扩展功能时,不必忍受运行时闭包膨胀和其他麻烦。这难道没有抓住多态性的全部要点吗?以正常的方式定义
任务
派生类的工作委托会更加优雅,即在基类中定义抽象函数

下面是如何做到这一点。诀窍在于挑战
public abstract class DeferredActionTask : Task
{
    private DeferredActionTask(Action _a, CancellationToken ct, TaskCreationOptions opts)
        : base(_ => _a(), null, ct, opts)
    {
        _a = this.action;
    }

    protected DeferredActionTask(
            CancellationToken ct = default(CancellationToken),
            TaskCreationOptions opts = TaskCreationOptions.None)
        : this(default(Action), ct, opts)
    {
    }

    protected abstract void action();
};
public abstract class DeferredFunctionTask<TResult> : Task<TResult>
{
    private DeferredFunctionTask(Func<TResult> _f, CancellationToken ct, TaskCreationOptions opts)
        : base(_ => _f(), null, ct, opts)
    {
        _f = this.function;
    }

    protected DeferredFunctionTask(
            CancellationToken ct = default(CancellationToken),
            TaskCreationOptions opts = TaskCreationOptions.None)
        : this(default(Func<TResult>), ct, opts)
    {
    }

    protected abstract TResult function();
};
 private async DeferredFunctionTask<int> WaitForStart(CancellationTokenSource c, string  serviceName)
    {


        var t = await Task.Run<int>(() =>
        {
            int ret = 0;
            for (int i = 0; i < 500000000; i++)
            {


                //ret += i;
                //if (i % 100000 == 0)
                //    Console.WriteLine(i);

                if (c.IsCancellationRequested)
                {
                    return ret;
                }
            }

            return ret;

        });


        return t;
    }