我创建了一个类,我使用ThreadPool作为执行手段,允许异步顺序执行任务。我的想法是,我将在后台运行串行任务的多个实例,但我不希望每个实例都有一个单独的专用线程。我想检查的是这个类是否真的是线程安全的。这是相当简短的,所以我想我会由专家在这里运行它,以防我遗漏了一些明显的东西。我省略了一些针对不同Action类型的一些方便重载。
/// <summary>
/// This class wraps ThreadPool.QueueUserWorkItem, but providing guaranteed ordering of queued tasks for this instance.
/// Only one task in the queue will execute at a time, with the order of execution matching the order of addition.
/// This is designed as a lighter-weight alternative to using a dedicated Thread for processing of sequential tasks.
/// </summary>
public sealed class SerialAsyncTasker
{
private readonly Queue<Action> mTasks = new Queue<Action>();
private bool mTaskExecuting;
/// <summary>
/// Queue a new task for asynchronous execution on the thread pool.
/// </summary>
/// <param name="task">Task to execute</param>
public void QueueTask(Action task)
{
if (task == null) throw new ArgumentNullException("task");
lock (mTasks)
{
bool isFirstTask = (mTasks.Count == 0);
mTasks.Enqueue(task);
//Only start executing the task if this is the first task
//Additional tasks will be executed normally as part of sequencing
if (isFirstTask && !mTaskExecuting)
RunNextTask();
}
}
/// <summary>
/// Clear all queued tasks. Any task currently executing will continue to execute.
/// </summary>
public void Clear()
{
lock (mTasks)
{
mTasks.Clear();
}
}
/// <summary>
/// Wait until all currently queued tasks have completed executing.
/// If no tasks are queued, this method will return immediately.
/// This method does not prevent the race condition of a second thread
/// queueing a task while one thread is entering the wait;
/// if this is required, it must be synchronized externally.
/// </summary>
public void WaitUntilAllComplete()
{
lock (mTasks)
{
while (mTasks.Count > 0 || mTaskExecuting)
Monitor.Wait(mTasks);
}
}
private void RunTask(Object state)
{
var task = (Action)state;
task();
mTaskExecuting = false;
RunNextTask();
}
private void RunNextTask()
{
lock (mTasks)
{
if (mTasks.Count > 0)
{
mTaskExecuting = true;
var task = mTasks.Dequeue();
ThreadPool.QueueUserWorkItem(RunTask, task);
}
else
{
//If anybody is waiting for tasks to be complete, let them know
Monitor.PulseAll(mTasks);
}
}
}
}
更新:我修改了代码以修复Simon所指出的主要错误。现在通过单元测试,但我仍然欢迎观察。
答案 0 :(得分:2)
不要这样做。 (或者至少避免建立自己的东西。)
使用System.Threading.Tasks内容(.NET 4.0中的新增功能)。创建一个Task [](大小取决于您想要的并行任务数),让他们在等待BlockingCollection时从CancellationToken读取工作项。您的WaitForAll
实施会触发您的令牌,并致电Task.WaitAll(Task[]),这将阻止所有任务完成。
答案 1 :(得分:1)
这是我的第二个答案,假设您无法使用.NET 4.0(并希望对现有代码进行评论)。
QueueTask将第一个任务排入队列,获取isFirstTask = true,然后启动一个新线程。但是,另一个线程可能会在第一个线程正在处理时排队,而Count == 0 =&gt; isFirstTask = true,然后生成另一个线程。
此外,如果任务执行抛出异常(可能不一定会使所有内容崩溃,具体取决于异常处理),WaitUntilAllComplete将无限期挂起,从而导致它跳过对RunNextTask()的调用。
你的WaitUntilAllComplete只是等待,直到没有更多的排队任务,而不是当前正在执行的任务正在执行(它们可能只是在ThreadPool中排队)或完成。
答案 2 :(得分:1)
它内置于4.0
How to: Create a Task Scheduler That Limits the Degree of Concurrency
您还可以使用自定义调度程序来实现默认调度程序不提供的功能,例如严格的先进先出(FIFO)执行顺序。以下示例演示如何创建自定义任务计划程序。此调度程序允许您指定并发度。
答案 3 :(得分:0)
我在您的SerialAsyncTasker
课程中看到了一些问题,但听起来您可能已经很好地掌握了这些问题,因此我不会详细介绍该主题(我可能会更详细地编辑我的答案)后来)。您在评论中指出您不能使用.NET 4.0功能,也不能使用Reactive Extensions backport。我建议您在专用线程上使用生产者 - 消费者模式和单个消费者。这完全符合您按顺序异步执行任务的要求。
注意:您必须强化代码以支持正常关闭,处理异常等。
public class SerialAsyncTasker
{
private BlockingCollection<Action> m_Queue = new BlockingCollection<Action>();
public SerialAsyncTasker()
{
var thread = new Thread(
() =>
{
while (true)
{
Action task = m_Queue.Take();
task();
}
});
thread.IsBackground = true;
thread.Start();
}
public void QueueTask(Action task)
{
m_Queue.Add(task);
}
}
太糟糕了,您无法使用.NET 4.0 BCL或Reactive Extension下载中的BlockingCollection,但不用担心。实际上自己实现它并不太难。您可以使用Stephen Toub's blocking queue作为起点,只需重命名一些内容。
public class BlockingCollection<T>
{
private Queue<T> m_Queue = new Queue<T>();
public T Take()
{
lock (m_Queue)
{
while (m_Queue.Count <= 0) Monitor.Wait(m_Queue);
return m_Queue.Dequeue();
}
}
public void Add(T value)
{
lock (m_Queue)
{
m_Queue.Enqueue(value);
Monitor.Pulse(m_Queue);
}
}
}
答案 4 :(得分:0)
public class ParallelExcecuter
{
private readonly BlockingCollection<Task> _workItemHolder;
public ParallelExcecuter(int maxDegreeOfParallelism)
{
_workItemHolder = new BlockingCollection<Task>(maxDegreeOfParallelism);
}
public void Submit(Action action)
{
_workItemHolder.Add(Task.Run(action).ContinueWith(t =>
{
_workItemHolder.Take();
}));
}
public void WaitUntilWorkDone()
{
while (_workItemHolder.Count < 0)
{
Monitor.Wait(_workItemHolder);
}
}
}