How to make TaskCompletionSource.Task complete using specific TaskScheduler

后端 未结 1 858
时光取名叫无心
时光取名叫无心 2021-01-15 19:48

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

相关标签:
1条回答
  • 2021-01-15 19:59

    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.

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

    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.

    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.

    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).

    0 讨论(0)
提交回复
热议问题