可借鉴的可恢复的异步任务队列

时间:2014-11-23 20:23:28

标签: c# .net async-await task-queue cancellation

我已根据我在此处找到的内容实现了工作队列> Task queue for wp8? ...但是无法使用它实现其他功能。

我取出了Func<Task>并将其替换为ICommands(持有自己的CancellationTokens),并打算添加Pause()Resume()Save()&amp; ; Restore()方法。这是OnFormClose()我可以暂停队列处理并提示用户决定是否“等待队列完成”(即恢复),或“立即退出”(即保存并退出)。 / p>

public class WqController
{
    private readonly Queue<ICommand> _queue = new Queue<ICommand>();
    private Task _queueProcessor;
    private ICommand _curCommand;

    public void Enqueue(ICommand command)
    {
        _queue.Enqueue(command);

        if (_queueProcessor == null) _queueProcessor = ProcessQueue();
    }

    private async Task ProcessQueue()
    {
        try
        {
            while (_queue.Count != 0)
            {
                _curCommand = _queue.Peek();

                try
                {
                    await Task.Run(() => _curCommand.Execute());
                }
                catch (OperationCanceledException)
                {
                    Console.WriteLine("QUEUE PAUSED");
                    return;
                }
                catch (Exception)
                {
                    Console.WriteLine("FAILED TO EXECUTE COMMAND");
                }
                _queue.Dequeue();
            }
        }
        finally
        {
            _queueProcessor = null;
            _curCommand = null;
        }
    }

    public async Task Cancel()
    {
        _curCommand.Cts.Cancel(true);
        await _queueProcessor;
    }

    public void Resume()
    {
        _queueProcessor = ProcessQueue();
    }
}

Save()&amp; Restore()工作正常,所以我没有把它们包含在这里。 Cancel()间歇地/不可靠地工作,而Restore()似乎根本不起作用(令我感到困惑,因为我基本上尝试了与Enqueue()中的工作相同的重启法)。

1 个答案:

答案 0 :(得分:0)

我得到了这个工作,并认为我应该在这里概述我的解决方案。

事实证明,我对取消令牌的使用有点偶然,这阻止了这个类按预期运行。例如,以下问题是相关的:

  1. 如果在命令中通过了最后一次取消检查后调用了Cancel,则会加载一个新命令(以及它自己的新取消令牌),因此取消调用将被丢失/忽略。这是在if (_curCommand.Cts.Token.IsCancellationRequested) return;之后_queue.Dequeue();解决的。

  2. 在调用取消后,如果稍后要恢复该命令,那么它将需要一个新的取消令牌(否则具有cancel = true的现有命令仍将处于活动状态)。行_curCommand.InvalidateCancellationToken();通过将令牌设置为null来执行此操作,然后我的命令在下次调用时刷新令牌。

  3. 我使用的完整代码:

    public class WqController
    {
        private readonly Queue<ICommand> _queue = new Queue<ICommand>();
        private Task _queueProcessor;
        private ICommand _curCommand;
    
        public void Enqueue(ICommand command)
        {
            _queue.Enqueue(command);
    
            if (_queueProcessor == null) _queueProcessor = ProcessQueue();
        }
    
        private async Task ProcessQueue()
        {
            try
            {
                while (_queue.Count != 0)
                {
                    _curCommand = _queue.Peek();
    
                    try
                    {
                        await Task.Run(() => _curCommand.Execute());
                    }
                    catch (OperationCanceledException)
                    {
                        _curCommand.InvalidateCancellationToken();
                        Console.WriteLine("QUEUE PAUSED");
                        return;
                    }
                    catch (Exception)
                    {
                        Console.WriteLine("FAILED TO EXECUTE COMMAND");
                    }
                    _queue.Dequeue();
                    if (_curCommand.Cts.Token.IsCancellationRequested) return;
                }
            }
            finally
            {
                _queueProcessor = null;
                _curCommand = null;
            }
        }
    
        public async Task Cancel()
        {
            _curCommand.Cts.Cancel(true);
            await _queueProcessor;
        }
    
        public void Resume()
        {
            _queueProcessor = ProcessQueue();
        }
    }
    

    这一切似乎现在都非常顺利,并且是我之前使用的后台工作队列实现的一个重大改进。