从两个delegate.BeginInvoke异步方法同时访问队列<t> </t>

时间:2012-01-17 10:23:40

标签: c# asynchronous thread-safety queue begininvoke

我对线程和异步处理缺乏经验,所以我不确定我所做的事情是否安全。

更新:不幸的是,这仅限于.Net 3.5所以没有ConcurrentQueue(除非有人为.Net 3.5实现了它或者知道替代实现?)

更新2:我发现this方法使用来自ConcurrentQueue的Mono实现的源(这是.Net 3.5的完全兼容的代码)。

我有一个对象是在内部管理Queue<MyObject>的工作。此类需要异步执行其操作(它将在Windows服务的OnStart和OnStop事件之间连续运行)。无论如何,我会让代码进行讨论:

public class MyObjectQueueManager : IMyObjectQueueManager
{
    private bool shouldContinueEnqueuing;
    private readonly Queue<MyObject> queue;
    private readonly IMyObjectIdentifier myObjectIdentifier;

    public MyObjectQueueManager( IMyObjectIdentifier myObjectIdentifier )
    {
        this.myObjectIdentifier = myObjectIdentifier;
        queue = new Queue<MyObject>();
    }

    public void StartQueue()
    {
        shouldContinueEnqueuing = true;

        var enqueuer = new MyObjectEnqueuer( EnqueueMyObjects );
        enqueuer.BeginInvoke( myObjectIdentifier, queue, StopEnqueuingMyObjects, new object() );

        var dequeuer = new MyObjectDequeuer( DequeueMyObjects );
        dequeuer.BeginInvoke( queue, StopDequeuingMyObjects, new object() );
    }

    public void StopQueue()
    {
        shouldContinueEnqueuing = false;
    }

    public event EventHandler<NextMyObjectEventArgs> OnNextMyObjectAvailable;

    private void EnqueueMyObjects( IMyObjectIdentifier identifier, Queue<MyObject> queue )
    {
        while ( shouldContinueEnqueuing )
        {
            var myObjects = identifier.GetMyObjects();

            foreach ( var myObject in myObjects )
            {
                queue.Enqueue( myObject );
            }

            WaitForQueueToShrink( queue, 1000 /* arbitrary queue limiter - will come from settings. */ );
        }
    }

    private void DequeueMyObjects( Queue<MyObject> queue )
    {
        while ( queue.Count > 0 || shouldContinueEnqueuing )
        {
            if ( queue.Count > 0 )
            {
                OnNextMyObjectAvailable( this, new NextMyObjectEventArgs( queue.Dequeue() ) );
            }

            Thread.Sleep( 1 );
        }
    }

    private static void WaitForQueueToShrink( ICollection queue, int queueLimit )
    {
        while ( queue.Count > queueLimit )
        {
            Thread.Sleep( 10 );
        }
    }

    private static void StopEnqueuingMyObjects( IAsyncResult result )
    {
        var asyncResult = ( AsyncResult ) result;
        var x = ( MyObjectEnqueuer ) asyncResult.AsyncDelegate;

        x.EndInvoke( asyncResult );
    }

    private static void StopDequeuingMyObjects( IAsyncResult result )
    {
        var asyncResult = ( AsyncResult ) result;
        var x = ( MyObjectDequeuer ) asyncResult.AsyncDelegate;

        x.EndInvoke( asyncResult );
    }

    private delegate void MyObjectEnqueuer( IMyObjectIdentifier myObjectIdentifier, Queue<MyObject> queue );

    private delegate void MyObjectDequeuer( Queue<MyObject> queue );
}

我的笔记/想法/关注:

  • 如果我理解了delegate.BeginInvoke,我正在产生两个线程,两者都在访问Queue(通过调用方法参数)。
  • 一个帖子只排队。
  • 一个线程只是将项目出列,它总是先检查项目是否在队列中。
  • 我不确定这是否是线程安全的 - 队列是否会被一个线程锁定而不能从另一个线程访问?
  • 如果是这样,我可以使用哪种替代方法/技术,这样我可以独立地将项目排队/出列而不会占用主工作线程?
  • 如果这种方法实际上是某种声音(你永远不知道)会扩展到处理大音量吗?即如果IMyObjectIdentifier每秒返回数千个项目,那么该方案将起作用(当然,我将进行体积测试以确认)。

作为奖励,我注意到有类似的代码用于从StopEnqueuingMyObjects和StopDequeuingMyObjects方法调用EndInvoke - 如果可以,我想重构使用单个方法但是当我尝试使用泛型方法时像这样:

private static void StopProcessingMyObjects<T>( IAsyncResult result )
{
    var asyncResult = ( AsyncResult ) result;
    var x = ( T ) asyncResult.AsyncDelegate;

    x.EndInvoke( asyncResult ); 
}

它不喜欢这样,因为它不知道T是一个delgate,因此无法调用EndInvoke。有可能这样做吗?

1 个答案:

答案 0 :(得分:3)

试试ConcurrentQueue<T>。这是一个线程安全的Queue实现。 API略有不同,因此可能需要对当前代码进行一些修改才能实现。