我正在尝试按顺序排队异步任务,所以我为此创建了一个类:
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() );
}
}
}
}
我这样用:
Func<Task> action = async () =>
{
Log("Entered");
await Task.Delay(5000);
Log("Exited");
}
m_taskSequentialQueue.EnqueueTask( action );
m_taskSequentialQueue.EnqueueTask( action );
我希望我的日志能够阅读:
Entered
Exited
Entered
Exited
相反,我得到:
Entered
Entered
Exited
Exited
我不确定我做错了什么。
由于
答案 0 :(得分:4)
当您在theTask.ContinueWith(
中StartNextTask
时,您正在继续的事情是内部任务的开始而不是内部任务的完成。一旦内部任务命中第一个await
,theTask
任务将被视为完成,因为函数已返回。
作为创可贴,你可以做到
if (theTask != null)
{
theTask.Start();
if(theTask is Task<Task>)
{
theTask = ((Task<Task>)theTask).Unwrap();
}
theTask.ContinueWith(...
但是我认为你的整个使用方法&#34;冷任务&#34;是有缺陷的。您应该使用Queue<Func<Task>>
而不是Queue<Task>
,这样可以让您的代码更加简单。
答案 1 :(得分:0)
Func<Task> action = async () =>
{
lock (lock_x)
{
Console.WriteLine("Entered");
Thread.Sleep(5000);
Console.WriteLine("Exited");
}
};
应该有效,因为您的队列实现是按顺序提交它们。