简体   繁体   English

顺序异步任务队列不等到异步任务结束

[英]Sequential async task queue does not wait until async task ends

I am trying to sequentially queue async tasks so I made a class for this: 我试图按顺序将异步任务排队,所以为此做了一个类:

 public class TaskSequentialQueue : IDisposable, ITaskSequentialQueue
    {
        public delegate void OnExeptionDelegate(Exception ex);

        private readonly Queue<Task> m_queue = new Queue<Task>();
        private readonly Object m_lock = new Object();
        private readonly CancellationTokenSource m_CancelToken = new CancellationTokenSource();
        private readonly OnExeptionDelegate m_onExeptionDelegate = null;
        private Task m_currentTask = null;
        private bool m_isDisposed = false;      

        public TaskSequentialQueue(OnExeptionDelegate expDelegate = null)
        {
            m_onExeptionDelegate = expDelegate;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool isDisposing)
        {
            if (m_isDisposed)
            {
                return;
            }

            if (isDisposing)
            {
                lock (m_lock)
                {
                    m_isDisposed = true;
                    m_queue.Clear();
                }

                m_CancelToken.Cancel();
                m_CancelToken.Dispose();
            }
        }

        public void EnqueueTask( Task task)
        {
            lock (m_lock)
            {
                if (m_isDisposed)
                    throw new ObjectDisposedException("TaskSequentialQueue");

                m_queue.Enqueue(task);
            }

            StartNextTask();
        }

        public void EnqueueTask( Func<Task> task)
        {
            EnqueueTask(new Task<Task>(task));
        }

        public Task EnqueueTaskAndWait( Task task)
        {
            TaskCompletionSource<int> taskSource = new TaskCompletionSource<int>();
            lock (m_lock)
            {
                if (m_isDisposed)
                    throw new ObjectDisposedException("TaskSequentialQueue");

                Func<Task> toDo = async () =>
                {                                        
                    var waitabletask = task.ContinueWith( antecedent => 
                                                            { 
                                                                taskSource.SetResult(0); 
                                                                if (antecedent.Exception != null)
                                                                    throw antecedent.Exception; 
                                                            });
                    task.Start();
                    await waitabletask;
                };
                this.EnqueueTask(toDo);
            }

            StartNextTask();

            return taskSource.Task;  //TODO! propagate the exception correctly ?
        }

        private void StartNextTask()
        {
            Task theTask = null;
            lock(m_lock)
            {
                if (m_currentTask == null && m_queue.Count > 0 && !m_isDisposed)
                {
                    m_currentTask = m_queue.Dequeue();
                    theTask = m_currentTask;
                }
            }      
            if (theTask != null)
            {
                theTask.Start();  
                theTask.ContinueWith( (antecedent) =>
                    {
                        Exception theEx = antecedent.Exception;
                        if (theEx == null && antecedent is Task<Task>)
                            theEx = (antecedent as Task<Task>)?.Result.Exception;

                        if (m_onExeptionDelegate != null && theEx != null)
                        {
                            try { m_onExeptionDelegate(theEx); } catch(Exception) {}
                        }

                        lock(m_lock)
                        {
                            m_currentTask = null;
                        }

                        Task.Run( () =>  StartNextTask() );
                }
            }
        }
    }

I use it like this: 我这样使用它:

     Func<Task> action = async () =>
                {
                   Log("Entered");
                   await Task.Delay(5000);
                   Log("Exited");
                }

m_taskSequentialQueue.EnqueueTask( action );
m_taskSequentialQueue.EnqueueTask( action );

I expect my log to read: 我希望我的日志显示为:

Entered
Exited
Entered
Exited

Instead I get: 相反,我得到:

Entered
Entered
Exited
Exited

I'm not sure what I'm doing wrong. 我不确定自己在做什么错。

Thanks 谢谢

When you do theTask.ContinueWith( in StartNextTask the thing you are continuing on is the starting of the inner task not the completion of the inner task. Once the inner task hits the first await the theTask task will be considered complete because the function returned. 当你theTask.ContinueWith(StartNextTask你继续在事情是内部任务内的任务没有完成的启动。一旦内任务到达第一个awaittheTask任务将被视为完成,因为该函数返回。

As a band-aid you could do 作为创可贴,你可以做

if (theTask != null)
{
    theTask.Start();  
    if(theTask is Task<Task>)
    {
       theTask = ((Task<Task>)theTask).Unwrap();
    }
    theTask.ContinueWith(...

However I think your whole method of using "Cold Tasks" is flawed. 但是,我认为您使用“冷任务”的整个方法都是有缺陷的。 You should just work with a Queue<Func<Task>> instead of a Queue<Task> and it will allow your code to be much simpler. 您应该只使用Queue<Func<Task>>而不是Queue<Task> ,它将使您的代码更加简单。

        Func<Task> action = async () =>
        {
            lock (lock_x)
            {
                Console.WriteLine("Entered");
                Thread.Sleep(5000);
                Console.WriteLine("Exited");
            }
        };

should work since your queue implementation is submitting them in order. 应该可以正常工作,因为您的队列实现是按顺序提交它们的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM