【问题标题】:Create ice cold TaskCompletionSource?创建冰冷的TaskCompletionSource?
【发布时间】:2015-02-05 09:28:25
【问题描述】:

我正在编写一个库,其中包括基于 .Net 任务的调度功能(不是标准的 TaskSchedulerIScheduler...)。我正在使用TaskCompletionSource,而Task.Status 对于表示底层操作的状态至关重要,包括 TaskStatus.Created,即已创建但尚未开始。我知道返回的任务通常应该很热,但是对于我手动控制的代理任务,我真的希望它们最初是 Created

不幸的是,TaskCompletionSource.Task 的初始状态是WaitingForActivation,即它已经超过了Created。换句话说,TaskCompletionSource 支持 两个 状态,但我需要 三个 状态:

问题:如何获得可以手动设置为三种不同状态的Task? IE。 Task.Status可以设置为:

1) Created
2)WaitingForActivation/WaitingForChildrenToComplete/WaitingToRun/Running之一
3)RanToCompletion/Canceled/Faulted中的任一个@

下面的代码抱怨类型不匹配是可以理解的。我可以通过将new Task<TResult> 更改为new Task<Task<TResult>> 来包装任务,但要回到Task<TResult> 我必须Unwrap() 它,并且未包装的任务将具有状态WaitingForActivation,让我回到第一方。

我会有很多这样的,所以用Wait()为每个线程阻塞不是一个选项。

我考虑过从 Task 继承并覆盖成员(使用 new),但如果可能的话,最好给图书馆用户一个实际的 Task 而不是 DerivedTask,特别是因为我向在许多其他地方也值得期待。

想法?

private TaskCompletionSource<TResult> tcs;

private async Task<TResult> CreateStartCompleteAsync()
{
    await tcs.Task;
    if (tcs.Task.IsCanceled)
    {
        throw new OperationCanceledException("");
    }
    else if // etc.
}

public ColdTaskCompletionSource()
{
    tcs = new TaskCompletionSource<TResult>();
    Task = new Task<TResult>(() => CreateStartCompleteAsync());
}

错误:
* 无法将 lambda 表达式转换为委托类型“System.Func”,因为块中的某些返回类型不能隐式转换为委托返回类型
* 无法将类型“System.Threading.Tasks.Task”隐式转换为“TResult”

【问题讨论】:

  • 我想我已经回答了一个密切相关的问题here
  • 我认为,如果您想拥有更多控制权,您的 API 不应基于 Task,而应基于自定义类,该类准确地公开您想要公开的信息。您也可以公开Task,但状态信息将来自您自己的班级。
  • @Noseratio:链接代码中的Task 仅经历两种状态:Created 和“已完成”,类似于 TaskCompletionSource.Task 仅经历两种状态(WaitingForActivation 和“已完成” ”)。我需要 3 个状态(CreatedRunning、“已完成”),并且我上面的示例代码尝试使用 async-await 来完成此操作,但正如所解释的那样由于类型而不起作用不匹配。我看不到如何修改您的链接代码以支持 3 状态 - 有什么想法吗?
  • @usr: 是的,我可以使用自定义状态信息创建自己的可等待对象,但我在很多地方都使用常规(非代理)任务,并希望呈现单个(标准,即任务)可等待类型给图书馆用户。关于第二个建议:给用户一个任务(具有状态)和我自己的单独状态属性也会令人困惑和不受欢迎。如果我误解了什么,请告诉我。
  • @KristianWedberg 我明白了。我认为这是对 TPL 的滥用,以仅在您的调度库中存在的概念来重载内部状态代码。我会创建一个class ScheduledTask { MyStatusEnum Status; Task Completed; }。尽管我认为这是一种反模式,但您甚至可以使这种类型成为可等待的。无论如何,您的问题是有效的,应该按要求回答。

标签: c# .net task-parallel-library async-await


【解决方案1】:

虽然我同意 @usr 在 cmets 中的观点,但从技术上讲,您仍然可以拥有提供这些状态的实现:

  1. Created
  2. WaitingToRun
  3. RanToCompletion/Canceled/Faulted

为避免使用Task.Wait 阻塞线程,您可以使用内部帮助程序TaskScheduler,它首先将任务从Created 转换为WaitingToRun,并最终转换为完成状态之一。

下面的代码说明了这个概念。它只是经过非常轻微的测试,可能不是完全线程安全的,也许可以改进以在多个任务之间共享 FakeTaskScheduler 的单个实例。

public class ColdTaskCompletionSource
{
    public sealed class FakeTaskScheduler : TaskScheduler
    {
        Task _task;

        public FakeTaskScheduler()
        {
        }

        protected override void QueueTask(Task task)
        {
            _task = task;
        }

        protected sealed override bool TryDequeue(Task task)
        {
            if (task != _task)
                return false;

            _task = null;
            return true;
        }

        protected override IEnumerable<Task> GetScheduledTasks()
        {
            if (_task == null)
                yield break;
            yield return _task;
        }

        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
        {
            return false;
        }

        public override int MaximumConcurrencyLevel
        {
            get { return 1; }
        }

        public bool Execute()
        {
            if (_task == null)
                return false;

            var task = _task;
            _task = null;
            return base.TryExecuteTask(task);
        }
    }

    readonly Task _task;
    readonly CancellationTokenSource _cts;
    readonly object _lock = new Object();
    readonly FakeTaskScheduler _ts = new FakeTaskScheduler();
    Action _completionAction = null;

    // helpers

    void InvokeCompletionAction()
    {
        if (_completionAction != null)
            _completionAction();
    }

    void Complete()
    {
        if (_task.Status != TaskStatus.WaitingToRun)
            throw new InvalidOperationException("Invalid Task state");
        _ts.Execute();
    }

    // public API

    public ColdTaskCompletionSource()
    {
        _cts = new CancellationTokenSource();
        _task = new Task(InvokeCompletionAction, _cts.Token);
    }

    public Task Task { get { return _task; } }

    public void Start()
    {
        _task.Start(_ts);
    }

    public void SetCompleted()
    {
        lock (_lock)
            Complete();
    }

    public void SetException(Exception ex)
    {
        lock (_lock)
        {
            _completionAction = () => { throw ex; };
            Complete();
        }
    }

    public void SetCancelled()
    {
        lock (_lock)
        {
            _completionAction = () =>
            {
                _cts.Cancel();
                _cts.Token.ThrowIfCancellationRequested();
            };
            Complete();
        }
    }
}

【讨论】:

  • @Noseratio:效果很好,非常感谢!上面的代码需要按照我的“规范”调用 Start()。我修改它以通过在 Start():lock(_lock) _task.Start(_ts); 和 Complete() 中获取锁来使调用 Start() 成为可选,而不是抛出我做的:if (_task.Status == TaskStatus.Created) _task.Start(_ts);
【解决方案2】:

你不能以合理的方式。

Task 状态在内部处理,手动创建任务的唯一 API 是使用 TaskCompletionSource。您也不能从Task 继承,因为Status 属性只是一个getter,并且您无法访问支持字段m_stateFlags,因为它是内部的。

但是,不合理的方法是创建一个等待TaskCompletionSource 的任务,而您控制任务的状态是控制TaskCompletionSource

var taskCompletionSource = new TaskCompletionSource<bool>();
var cancellationTokenSource = new CancellationTokenSource();
var task = new Task(() => taskCompletionSource.Task.Wait(cancellationTokenSource.Token), cancellationTokenSource.Token); // task.Status == TaskStatus.Created

task.Start(); // task.Status == TaskStatus.Running

taskCompletionSource.SetResult(false) // task.Status == TaskStatus.RanToCompletion

或者

taskCompletionSource.SetException(new Exception("")) // task.Status == TaskStatus.Faulted

或者

cancellationTokenSource.Cancel() // task.Status == TaskStatus.Cancelled

我并不是真的建议你这样做。我不知道你为什么要控制Task 的状态,但你可能需要创建自己的结构来管理自己,而不是强迫Task 进入它不适合的设计。

【讨论】:

  • 为什么创建一个热运行的Task 只是为了用Task.Wait() 阻塞一个池线程?用冷酷的Task 模拟TaskCompletionSource 是相当的possible。它可能也不完美,因为它将内部 Task 暴露给调用者,但 IMO 仍然比阻塞更好。
  • @Noseratio "我怎样才能获得我手动设置状态的任务(已创建、正在运行、RanToCompletion、已取消、已故障)"
  • @i3arnon:编辑了这个问题 - 我有很多这样的问题,Wait() 不是一个选项。
猜你喜欢
  • 1970-01-01
  • 2014-09-29
  • 1970-01-01
  • 2016-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多