的AutoResetEvent没有阻止正常正常、AutoResetEvent

2023-09-03 05:31:02 作者:Smile.過期℡

我有一个线程,它创建了一个可变的工作线程数目以及它们之间分配任务。这是通过螺纹解决了的任务队列的对象,其实现您将在下面看到。

I have a thread, which creates a variable number of worker threads and distributes tasks between them. This is solved by passing the threads a TaskQueue object, whose implementation you will see below.

这些工作线程简单迭代的任务队列的对象,他们被赋予,执行各项任务。

These worker threads simply iterate over the TaskQueue object they were given, executing each task.

private class TaskQueue : IEnumerable<Task>
{
    public int Count
    {
        get
        {
            lock(this.tasks)
            {
                return this.tasks.Count;
            }
        }
    }

    private readonly Queue<Task> tasks = new Queue<Task>();
    private readonly AutoResetEvent taskWaitHandle = new AutoResetEvent(false);

    private bool isFinishing = false;
    private bool isFinished = false;

    public void Enqueue(Task task)
    {
        Log.Trace("Entering Enqueue, lock...");
        lock(this.tasks)
        {
            Log.Trace("Adding task, current count = {0}...", Count);
            this.tasks.Enqueue(task);

            if (Count == 1)
            {
                Log.Trace("Count = 1, so setting the wait handle...");
                this.taskWaitHandle.Set();
            }
        }
        Log.Trace("Exiting enqueue...");
    }

    public Task Dequeue()
    {
        Log.Trace("Entering Dequeue...");
        if (Count == 0)
        {
            if (this.isFinishing)
            {
                Log.Trace("Finishing (before waiting) - isCompleted set, returning empty task.");
                this.isFinished = true;
                return new Task();
            }

            Log.Trace("Count = 0, lets wait for a task...");
            this.taskWaitHandle.WaitOne();
            Log.Trace("Wait handle let us through, Count = {0}, IsFinishing = {1}, Returned = {2}", Count, this.isFinishing);

            if(this.isFinishing)
            {
                Log.Trace("Finishing - isCompleted set, returning empty task.");
                this.isFinished = true;
                return new Task();
            }
        }

        Log.Trace("Entering task lock...");
        lock(this.tasks)
        {
            Log.Trace("Entered task lock, about to dequeue next item, Count = {0}", Count);
            return this.tasks.Dequeue();
        }
    }

    public void Finish()
    {
        Log.Trace("Setting TaskQueue state to isFinishing = true and setting wait handle...");
        this.isFinishing = true;

        if (Count == 0)
        {
            this.taskWaitHandle.Set();
        }
    }

    public IEnumerator<Task> GetEnumerator()
    {
        while(true)
        {
            Task t = Dequeue();
            if(this.isFinished)
            {
                yield break;
            }

            yield return t;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

正如你所看到的,我使用的是的AutoResetEvent 对象,以确保工作线程不要退出prematurely,即之前得到任何任务。

As you can see, I'm using an AutoResetEvent object to make sure that the worker threads don't exit prematurely, i.e. before getting any tasks.

在一言以蔽之:

在主线程被分配一个任务线程的 Enqeueue 的-ing一个任务,其任务队列 在主线程通知没有更多的任务调用任务队列的完成的()方法来执行线程 工作线程获取分配给它的下一个任务调用任务队列的出列的()方法 the main thread assigns a task to a thread by Enqeueue-ing a task to its TaskQueue the main thread notifies the thread that are no more tasks to execute by calling the TaskQueue's Finish() method the worker thread retrieves the next task assigned to it by calling the TaskQueue's Dequeue() method

问题是,在出列()方法通常引发InvalidOperationException ,称该队列为空。正如你可以看到我加了一些记录,并且事实证明,该的的AutoResetEvent 的不堵塞的出列()的,即使没有调用它的设置()的方法。

The problem is that the Dequeue() method often throws an InvalidOperationException, saying that the Queue is empty. As you can see I added some logging, and it turns out, that the AutoResetEvent doesn't block the Dequeue(), even though there were no calls to its Set() method.

据我所知,调用AutoResetEvent.Set()将允许一个等待的线程进行(谁previously称为AutoResetEvent.WaitOne()),然后自动调用AutoResetEvent.Reset(),阻止下一个服务员。

As I understand it, calling AutoResetEvent.Set() will allow a waiting thread to proceed (who previously called AutoResetEvent.WaitOne()), and then automatically calls AutoResetEvent.Reset(), blocking the next waiter.

那么,什么可能是错的?难道我得到什么了吗?我有一个错误的地方? 我坐在上面这3小时,但我无法弄清楚什么是错的。 请帮帮我!

So what can be wrong? Did I get something wrong? Do I have an error somewhere? I'm sitting above this for 3 hours now, but I cannot figure out what's wrong. Please help me!

非常感谢你!

推荐答案

您离队code不正确。你检查Count下锁,然后通过你的裤子的接缝处飞,然后你期望的工作有什么东西。当你释放了锁,你不能保留的假设:)。您计数检查和tasks.Dequeue 必须锁下发生:

Your dequeue code is incorrect. You check the Count under lock, then fly by the seams of your pants, and then you expect the tasks to have something. You cannot retain assumptions while you release the lock :). Your Count check and tasks.Dequeue must occur under lock:

bool TryDequeue(out Tasks task)
{
  task = null;
  lock (this.tasks) {
    if (0 < tasks.Count) {
      task = tasks.Dequeue();
    }
  }
  if (null == task) {
    Log.Trace ("Queue was empty");
  }
  return null != task;
 }

您排队()code同样千疮百孔。你入队/出队并不能确保进度(你将不得不等待阻塞的线程出队即使在队列中的项目)。你的签名排队()是错误的。总体来说你的职位是非常非常差code。坦率地说,我想你想咀嚼比你更可以在这里咬......哦,和从不上锁日志。

You Enqueue() code is similarly riddled with problems. Your Enqueue/Dequeue don't ensure progress (you will have dequeue threads blocked waiting even though there are items in the queue). Your signature of Enqueue() is wrong. Overall your post is very very poor code. Frankly, I think you're trying to chew more than you can bite here... Oh, and never log under lock.

我强烈建议你只使用 ConcurrentQueue 。

I strongly suggest you just use ConcurrentQueue.

如果您没有访问到.NET 4.0这里是一个实现,让你开始:

If you don't have access to .Net 4.0 here is an implementation to get you started:

public class ConcurrentQueue<T>:IEnumerable<T>
{
    volatile bool fFinished = false;
    ManualResetEvent eventAdded = new ManualResetEvent(false);
    private Queue<T> queue = new Queue<T>();
    private object syncRoot = new object();

    public void SetFinished()
    {
        lock (syncRoot)
        {
            fFinished = true;
            eventAdded.Set();
        }
    }

    public void Enqueue(T t)
    {
        Debug.Assert (false == fFinished);
        lock (syncRoot)
        {
            queue.Enqueue(t);
            eventAdded.Set();
        }
    }

    private bool Dequeue(out T t)
    {
        do
        {
            lock (syncRoot)
            {
                if (0 < queue.Count)
                {
                    t = queue.Dequeue();
                    return true;
                }
                if (false == fFinished)
                {
                    eventAdded.Reset ();
                }
            }
            if (false == fFinished)
            {
                eventAdded.WaitOne();
            }
            else
            {
                break;
            }
        } while (true);
        t = default(T);
        return false;
    }


    public IEnumerator<T> GetEnumerator()
    {
        T t;
        while (Dequeue(out t))
        {
            yield return t;
        }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}