Как сделать TaskCompletionSource.Task завершенным, используя определенный TaskScheduler
Как сделать доработку TaskCompletionSource.Task
случиться на конкретном TaskScheduler
когда я звоню TaskCompletionSource.SetResult
?
В настоящее время я использую идею, которую я позаимствовал из этого поста:
static public Task<TResult> ContinueOnTaskScheduler<TResult>(
this Task<TResult> @this, TaskScheduler scheduler)
{
return @this.ContinueWith(
antecedent => antecedent,
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
scheduler).Unwrap();
}
Поэтому, когда бы я ни вернулся TaskCompletionSource.Task
звонящему я сейчас вернусь TaskCompletionSource.Task.ContinueOnTaskScheduler(scheduler)
вместо.
Можно ли как-то избежать этого другого уровня косвенности ContinueWith
?
1 ответ
Было бы интересно узнать ваши цели за этим. Во всяком случае, если вы хотите избежать накладных расходов ContinueWith
(который я считаю довольно низким), вам, вероятно, придется придумать свою собственную версию шаблона, похожую на TaskCompletionSource
,
Это не так сложно. Например, что-то вроде Promise
ниже могут быть использованы так же, как вы используете TaskCompletionSource
, но позволил бы предоставить кастом TaskScheduler
для завершения (отказ от ответственности: почти не проверен):
public class Promise
{
readonly Task _task;
readonly CancellationTokenSource _cts;
readonly object _lock = new Object();
Action _completionAction = null;
// public API
public Promise()
{
_cts = new CancellationTokenSource();
_task = new Task(InvokeCompletionAction, _cts.Token);
}
public Task Task { get { return _task; } }
public void SetCompleted(TaskScheduler sheduler = null)
{
lock(_lock)
Complete(sheduler);
}
public void SetException(Exception ex, TaskScheduler sheduler = null)
{
lock (_lock)
{
_completionAction = () => { throw ex; };
Complete(sheduler);
}
}
public void SetException(System.Runtime.ExceptionServices.ExceptionDispatchInfo edi, TaskScheduler sheduler = null)
{
lock (_lock)
{
_completionAction = () => { edi.Throw(); };
Complete(sheduler);
}
}
public void SetCancelled(TaskScheduler sheduler = null)
{
lock (_lock)
{
// don't call _cts.Cancel() outside _completionAction
// otherwise the cancellation won't be done on the sheduler
_completionAction = () =>
{
_cts.Cancel();
_cts.Token.ThrowIfCancellationRequested();
};
Complete(sheduler);
}
}
// implementation
void InvokeCompletionAction()
{
if (_completionAction != null)
_completionAction();
}
void Complete(TaskScheduler sheduler)
{
if (Task.Status != TaskStatus.Created)
throw new InvalidOperationException("Invalid task state.");
_task.RunSynchronously(sheduler?? TaskScheduler.Current);
}
}
Кстати, эта версия имеет переопределение для SetException(ExceptionDispatchInfo edi)
, чтобы вы могли распространять состояние активного исключения изнутри catch
:
catch(Exception ex)
{
var edi = ExceptionDispatchInfo.Capture(ex);
promise.SetException(edi);
}
Также легко создать общую версию этого.
Однако у этого подхода есть и обратная сторона. Третья сторона может сделать promise.Task.Run
или же promise.Task.RunSynchronously
как Task
выставлен в TaskStatus.Created
государство.
Вы можете добавить чек для этого в InvokeCompletionAction
или, возможно, вы могли бы скрыть это, используя вложенные задачи / Task.Unwrap
(хотя последний принес бы некоторые накладные расходы обратно).