17

我原来的方法是这样的:

string DoSomeWork();

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

现在我想将签名更改为 return Task,因此用户可以等待。

Task DoSomeWork();

同时,我仍然需要返回执行 ID(例如用于跟踪目的),并且我看到了一些选项。首先,使用一个out参数,其次,返回包含执行 ID 和任务的元组(在 C# 中,这看起来不是最佳选择),第三,我真正想问的问题。

如果我创建一个派生类怎么办Task

public class ExtendedTask : Task
{
     public string ExecutionID {get; set;}
}

这看起来好吗?还是决定其他选择更好?

PS 在 BCL 中有一些类派生自Task.

UPDATE,似乎我无法足够清楚地定义这一点。但是我需要在作业完成之前访问 ExecutionID,所以我不能使用Task.Result.

4

3 回答 3

17

我不会亲自扩展 Task<T>,而是编写它。这样你就不需要担心任何只返回Task<T>的 API - 你可以包装任务。您可以拥有一个公开底层任务的属性,并且出于 C# 5 异步目的,您可以在自己的类型上实现等待者模式 - 但在我看来,创建自己的派生类型可能弊大于利。不过,这主要是一种直觉。

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

于 2012-02-13T18:02:07.783 回答
12

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

例如,在您的情况下,有类似的东西可能是有意义的:

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" };
     });
}

根据评论进行编辑:

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

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>
}

这仍然可以让您访问有关您的进程的信息,以及Task/ Task<T>

于 2012-02-13T17:55:18.137 回答
4

如果您决定Taskor继承Task<TResult>,您可能会遇到挫折,即为任务提供实际工作的Action<Object>orFunc<Object,TResult>委托必须在构建 Task 派生对象时指定,并且以后无法更改。即使基类构造函数不是Start()新创建的任务也是如此,事实上它可能直到很久以后才会启动,如果有的话。

这使得Task在实例必须在其最终工作的全部细节可用之前创建的情况下使用 - 派生类变得困难。

一个例子可能是一个由众所周知的节点组成的无定形网络,它们致力于一个共同的目标,这样它们就可以以自组织的方式Task<TResult>访问彼此的Result属性。保证您可以在网络中的任意节点上运行的最简单方法是在启动任何节点之前预先构建所有节点。这巧妙地避免了尝试分析工作图依赖关系的问题,并允许运行时因素确定何时、是否以及以何种顺序需要值。Wait()Result

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

好像这还不够,还有其他原因,不得不使用 lambda 函数来提供所需的函数是不方便的。因为它作为参数传递到构造函数中,所以函数无法访问this最终任务实例的指针,这使得代码很难看,特别是考虑到 lambda 必须定义在 - 并且可能无意中关闭 -一些不相​​关this的指针。

我可以继续说下去,但最重要的是,在派生类中定义扩展功能时,您不必忍受运行时闭包膨胀和其他麻烦。这不会错过多态性的全部意义吗?以正常方式定义派生类的工作委托会更优雅Task,即基类中的抽象函数。

这是如何做到的。诀窍是定义一个私有构造函数,它关闭它自己的参数之一。由(链接的)被调用者传递的参数null充当占位符变量,您可以将其关闭以创建Task基类所需的委托。一旦进入构造函数体,“this”指针就可用,因此您可以将实际函数指针替换为封闭参数,替换null. 请注意,这样做不会“为时已晚”,因为外部委托还不可能被调用。

从“任务”派生:

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();
};

从“Task<TResult>”派生:

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();
};

请记住,与构造Task实例的任何其他用途一样,构造实例Task不会在构造自动启动,因此使用此技术您仍然必须Start()在稍后的某个时间显式调用。当然,如上所述,就是重点。

最后,请注意,我让私有构造函数始终传递基构造函数null的参数,这实际上阻止了将只读属性设置为有用的值。如果您愿意,您可以将其更改为包括传递这样的值,但这里的原因再次是,重点是消除对启动数据进行预先确定的要求。几乎没有意义——当你现在有你自己的整个派生类来填充,在调用之前的任何时间,用相关的实例数据——必须在逻辑上不相关的时间,很可能提前很长时间,准确地挑出一个“特殊" 数据参数,表示任务最终有用工作的详细信息。stateTaskAsyncStateStart

于 2016-02-15T23:24:56.980 回答