如何使用特定的TaskScheduler使TaskCompletionSource.Task完整如何使用、完整、TaskCompletionSource、TaskScheduler

2023-09-04 22:37:58 作者:有些人只能埋藏于心

如何制作完成 TaskCompletionSource.Task 的发生在特定的的TaskScheduler ,当我打电话 TaskCompletionSource.SetResult

How to make the completion of TaskCompletionSource.Task happen on specific TaskScheduler, when I call TaskCompletionSource.SetResult?

目前,我用我的这个帖子借来的想法:

Currently, I'm using the idea I borrowed from this post:

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(调度)代替。

So whenever I would return TaskCompletionSource.Task to the caller, I now return TaskCompletionSource.Task.ContinueOnTaskScheduler(scheduler) instead.

是否有可能以某种方式避免这种 ContinueWith

Is it possible to somehow avoid this another level of indirection of ContinueWith?

推荐答案

这将是有趣的知道这背后你的目标。无论如何,如果你想避免 ContinueWith 的开销(我认为这是相当低的),你可能要拿出你自己的版本类似的模式 TaskCompletionSource

It would be interesting to know your goals behind this. Anyway, if you like to avoid the overhead of ContinueWith (which I think is quite low), you'd probably have to come up with your own version of a pattern similar to TaskCompletionSource.

这不是那么复杂。例如,像无极下面可以使用 TaskCompletionSource 以同样的方式被使用,但将允许提供自定义的TaskScheduler 完成(免责声明:几乎未经):

It's not that complex. E.g., something like Promise below can be used in the same way you use TaskCompletionSource, but would allow to provide a custom TaskScheduler for completion (disclaimer: almost untested):

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),所以你可以从抓内部传播活动异常的状态

On a side note, this version has an override for SetException(ExceptionDispatchInfo edi), so you could propagate the active exception's state from inside catch:

catch(Exception ex)
{
    var edi = ExceptionDispatchInfo.Capture(ex);
    promise.SetException(edi);
}

可以很容易地创建这样的通用版本了。

It's easy to create a generic version of this, too.

有这种方法的缺点是,虽然。第三方可以做 promise.Task.Run promise.Task.RunSynchronously ,因为工作 TaskStatus.Created 状态暴露出来。

There's a downside of this approach, though. A 3rd party can do promise.Task.Run or promise.Task.RunSynchronously, as the Task is exposed in the TaskStatus.Created state.

您可以添加该支票到 InvokeCompletionAction ,或者你可以用它嵌套的任务可能隐藏/ Task.Unwrap (尽管后者会带来一些开销后)。

You could add a check for that into InvokeCompletionAction, or you could probably hide it using nested tasks / Task.Unwrap (although the latter would bring some overhead back).