可以从TPL任务派生从方法返回更多细节吗?
我的原始方法如下:
string DoSomeWork();
方法DoSomeWork
在其他线程上启动一些工作并返回执行ID(只是随机字符串)。 稍后我可以通过给定的执行ID查询结果。 重点是在作业完成之前使执行ID可用。
现在我想更改签名以返回Task,因此用户可以等待。
Task DoSomeWork();
同时我仍然需要返回执行ID(例如用于跟踪),我看到几个选项。 首先,如果out
参数,第二个是返回带有执行ID和任务的元组(在C#中,这看起来不是最佳选项),第三个是我实际想要问的。
如果我将创建将派生自Task
类的类,该怎么办:
public class ExtendedTask : Task { public string ExecutionID {get; set;} }
这看起来不错吗? 或者最好决定其他选择?
PS在BCL中有一些派生自Task类。
更新 ,似乎我无法定义这个明确的enouthg。 但是我需要在作业完成之前访问ExecutionID,因此我不能使用Task.Result
。
我不会亲自扩展 Task
,我会把它组成 。 这样您就不必担心任何只返回Task
API – 您可以将任务包装起来。 你可以拥有一个暴露底层任务的属性,而对于C#5异步目的,你可以在你自己的类型上实现awaiter模式 – 但我觉得创建你自己的派生类型可能弊大于利。 尽管如此,这主要是一种直觉。
另一种选择是以相反的方式工作:将您的额外状态存储在Task.AsyncState
属性中; 毕竟,这就是它的用途。 这样,您可以轻松地传递任务,而不会丢失它在逻辑上属于的执行上下文。
我建议使用Task
,因为它允许您在任务的结果中“嵌入”其他信息。
例如,在您的情况下,有类似的东西可能是有意义的:
class ExecutionResult { public int ExecutionID { get; set; } public string Result { get; set; } // ... } public Task DoSomeWork() { return Task.Factory.StartNew( () => { // Replace with real work, etc... return new ExecutionResult { ExecutionID = 0, Result = "Foo" }; }); }
编辑以回应评论:
如果你需要“之前”任务完成的数据,并且为了其他目的而试图访问它,我建议创建一个包含Task和其他数据的类,并返回它,即:
class ExecutionResult { public int ExecutionID { get; private set; } public Task 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 }
这仍然可以让您访问有关您的流程和Task
/ Task
。
如果您决定从Task
或Task
inheritance,您可能会遇到这样的挫折: 必须在您的任务时指定为任务提供实际工作的Action
或Func
委托。派生对象是构造的,以后不能更改。 即使基类构造函数没有Start()
新创建的任务也是如此,事实上它可能直到很久以后才开始,如果有的话。
这使得在必须在其最终工作的完整细节可用之前创建实例的情况下,难以使用Task
-derived类。
一个示例可能是众所周知的Task
节点的无定形网络,这些节点在共享目标上工作,以便它们以临时方式访问彼此的Result
属性。 保证在网络中的任意节点上都可以使用Wait()
的最简单方法是在启动任何节点之前预先构造所有节点。 这巧妙地避免了尝试分析工作图依赖性的问题,并允许运行时因素确定何时,是否以及以什么顺序要求Result
值。
这里的问题是,对于某些节点,您可能无法提供在构造时定义工作的function。 如果创建必要的lambda函数需要从网络中的其他任务中关闭Result
值,那么提供我们想要的Result
的Task
可能尚未构建。 即使它恰好在预构建阶段就已经构建过了,你也不能在它上面调用Start()
,因为它可能会依赖于其他没有的节点。 请记住,预构建网络的重点是避免像这样的复杂性。
好像这还不够,还有其他原因,不得不使用lambda函数来提供所需的function。 因为它作为参数传递给构造函数,所以函数无法访问最终任务实例的this
指针,这会产生丑陋的代码,特别是考虑到lambda必然是在 – 范围内定义 – 并且可能无意中关闭 – – 一些不相关的指针。
我可以继续,但最重要的是,在派生类中定义扩展function时,您不应该忍受运行时闭包膨胀和其他麻烦。 这不是错过了多态性的全部意义吗? 以正常方式定义Task
-derived类的工作委托更为优雅,即基类中的抽象函数。
这是怎么做的。 诀窍是定义一个私有构造函数,它关闭一个自己的参数。 最初设置为null
的参数充当占位符变量,您可以将其关闭以创建Task
基类所需的委托。 一旦你进入构造函数体,’this’指针就可用了,所以你可以在实际的函数指针中进行补丁。
从’任务’派生:
public abstract class DeferredActionTask : Task { private DeferredActionTask(DeferredActionTask _this) : base(_ => ((Func)_)().action(), (Func )(() => _this)) { _this = this; } protected DeferredActionTask() : this(null) { } protected abstract void action(); };
从’Task
public abstract class DeferredFunctionTask : Task { private DeferredFunctionTask(DeferredFunctionTask _this) : base(_ => ((Func>)_)().function(), (Func>)(() => _this)) { _this = this; } protected DeferredFunctionTask() : this(null) { } protected abstract TResult function(); };
[编辑:简体]
这些简化版本通过直接关闭派生实例的动作或函数方法,进一步减少了无关闭。 这也可以释放基类中的AsyncState
,以防您想要使用它。 现在几乎没有必要,因为你现在拥有自己的整个派生类; 因此, AsyncState
不会传递给工作函数。 如果您需要它,您可以随时从基类的属性中获取它。 最后,各种可选参数现在可以传递给Task
基类。
从’任务’派生:
public abstract class DeferredActionTask : Task { private DeferredActionTask(Action _a, Object state, CancellationToken ct, TaskCreationOptions opts) : base(_ => _a(), state, ct, opts) { _a = this.action; } protected DeferredActionTask( Object state = null, CancellationToken ct = default(CancellationToken), TaskCreationOptions opts = TaskCreationOptions.None) : this(default(Action), state, ct, opts) { } protected abstract void action(); };
从’Task
public abstract class DeferredFunctionTask : Task { private DeferredFunctionTask(Func _f, Object state, CancellationToken ct, TaskCreationOptions opts) : base(_ => _f(), state, ct, opts) { _f = this.function; } protected DeferredFunctionTask( Object state = null, CancellationToken ct = default(CancellationToken), TaskCreationOptions opts = TaskCreationOptions.None) : this(default(Func ), state, ct, opts) { } protected abstract TResult function(); };
private async DeferredFunctionTask WaitForStart(CancellationTokenSource c, string serviceName) { var t = await Task.Run (() => { 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; }
错误CS1983异步方法的返回类型必须为void,Task或Task