创建冰冷的 TaskCompletionSource?

Create ice cold TaskCompletionSource?

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

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

问题:我怎样才能得到一个可以手动设置为three[的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 继承并覆盖成员(使用新的),但如果可能的话,最好给库用户一个实际的 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',因为块中的某些 return 类型不能隐式转换为委托 return 类型
* 无法将类型 'System.Threading.Tasks.Task' 隐式转换为 'TResult'

你不能以合理的方式。

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 进入它不适合的设计。

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

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