Aborting a long running task in TPL

后端 未结 1 1022
时光说笑
时光说笑 2020-12-14 18:43

Our application uses the TPL to serialize (potentially) long running units of work. The creation of work (tasks) is user-driven and may be cancelled at any time. In order

相关标签:
1条回答
  • 2020-12-14 19:22

    The real issue here is that the long-running call in DoWork is not cancellation-aware. If I understand correctly, what you're doing here is not really cancelling the long-running work, but merely allowing the continuation to execute and, when the work completes on the cancelled task, ignoring the result. For example, if you used the inner task pattern to call CrunchNumbers(), which takes several minutes, cancelling the outer task will allow continuation to occur, but CrunchNumbers() will continue to execute in the background until completion.

    I don't think there's any real way around this other than making your long-running calls support cancellation. Often this isn't possible (they may be blocking API calls, with no API support for cancellation.) When this is the case, it's really a flaw in the API; you may check to see if there are alternate API calls that could be used to perform the operation in a way that can be cancelled. One hack approach to this is to capture a reference to the underlying Thread being used by the Task when the Task is started and then call Thread.Interrupt. This will wake up the thread from various sleep states and allow it to terminate, but in a potentially nasty way. Worst case, you can even call Thread.Abort, but that's even more problematic and not recommended.


    Here is a stab at a delegate-based wrapper. It's untested, but I think it will do the trick; feel free to edit the answer if you make it work and have fixes/improvements.

    public sealed class AbandonableTask
    {
        private readonly CancellationToken _token;
        private readonly Action _beginWork;
        private readonly Action _blockingWork;
        private readonly Action<Task> _afterComplete;
    
        private AbandonableTask(CancellationToken token, 
                                Action beginWork, 
                                Action blockingWork, 
                                Action<Task> afterComplete)
        {
            if (blockingWork == null) throw new ArgumentNullException("blockingWork");
    
            _token = token;
            _beginWork = beginWork;
            _blockingWork = blockingWork;
            _afterComplete = afterComplete;
        }
    
        private void RunTask()
        {
            if (_beginWork != null)
                _beginWork();
    
            var innerTask = new Task(_blockingWork, 
                                     _token, 
                                     TaskCreationOptions.LongRunning);
            innerTask.Start();
    
            innerTask.Wait(_token);
            if (innerTask.IsCompleted && _afterComplete != null)
            {
                _afterComplete(innerTask);
            }
        }
    
        public static Task Start(CancellationToken token, 
                                 Action blockingWork, 
                                 Action beginWork = null, 
                                 Action<Task> afterComplete = null)
        {
            if (blockingWork == null) throw new ArgumentNullException("blockingWork");
    
            var worker = new AbandonableTask(token, beginWork, blockingWork, afterComplete);
            var outerTask = new Task(worker.RunTask, token);
            outerTask.Start();
            return outerTask;
        }
    }
    
    0 讨论(0)
提交回复
热议问题