I'm writing a library which includes scheduling functionality (not the standard TaskScheduler
, IScheduler
...) based on .Net Tasks. I'm using TaskCompletionSource
, and Task.Status
is critical for representing the status of underlying operations, including TaskStatus.Created
, i.e. created but not yet started. I know returned Tasks should normally be hot, but for my manually controlled proxy Tasks, I really do want them initially as Created
.
Unfortunately for me, the initial status of TaskCompletionSource.Task
is WaitingForActivation
, i.e. it's already gone past Created
. Put differently, TaskCompletionSource
supports two states, but I need three states:
Question: How can I get a Task
that I can manually set to three different states? I.e. Task.Status
can be set to:
1) Created
2) One of WaitingForActivation
/WaitingForChildrenToComplete
/WaitingToRun
/Running
3) Either of RanToCompletion
/Canceled
/Faulted
The code below understandably complains about the type mismatch. I can instead wrap the Task by changing new Task<TResult>
to new Task<Task<TResult>>
, but to get back to Task<TResult>
I have to Unwrap()
it, and the unwrapped task will have status WaitingForActivation
, bringing me back to square one.
I will have a large number of these, so blocking a thread with Wait()
for each is not an option.
I have considered inheriting from Task
and overriding members (using new), but if possible it would be nice to give the library user an actual Task
instead of a DerivedTask
, especially since I present regular Tasks to also be awaited in many other places.
Ideas?
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());
}
Errors:
* Cannot convert lambda expression to delegate type 'System.Func' because some of the return types in the block are not implicitly convertible to the delegate return type
* Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'TResult'
While I agree with @usr's points in the comments, technically you still can have an implementation that provides these states:
Created
WaitingToRun
RanToCompletion/Canceled/Faulted
To avoid blocking threads with Task.Wait
, you could use an internal helper TaskScheduler
, which would first transition the task from Created
to WaitingToRun
and, eventually, to one of the completed states.
The code below illustrates this concept. It was only very slightly tested, might be not fully thread-safe and perhaps could be improved to share a single instance of FakeTaskScheduler
across multiple tasks.
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();
}
}
}
You can't in a reasonable way.
The Task
status is handled internally and the only API for creating tasks manually is using TaskCompletionSource
. You also can't inherit from Task
since the Status
property is only a getter and you can't reach the backing field m_stateFlags
since it's internal.
However, the unreasonable way would be to create a task that waits on a TaskCompletionSource
and you control the task's status be controlling the 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
Or
taskCompletionSource.SetException(new Exception("")) // task.Status == TaskStatus.Faulted
Or
cancellationTokenSource.Cancel() // task.Status == TaskStatus.Cancelled
I'm not really recommending you do that. I'm not sure why you want to control Task
's status but you probably need to create your own construct you can manage yourself instead of forcing Task
into a design it wasn't meant for.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With