Nito.AsyncEx异步生产者/消费者队列不处理

时间:2015-10-06 23:08:53

标签: c# multithreading silverlight async-await

我正在尝试创建一个"消息泵"类似于在线程上运行的自定义同步上下文。

该程序是Silverlight 5,同步队列来自Stephen Cleary的Nito.AsyncEx nuget(v3.0.1)。

代码(抱歉长度,故意包含评论/调试):

public sealed class ThreadSynchronizationContext : SynchronizationContext, IDisposable
{

    /// <summary>The queue of work items.</summary>
    private readonly AsyncProducerConsumerQueue<KeyValuePair<SendOrPostCallback, object>> syncQueue =
        new AsyncProducerConsumerQueue<KeyValuePair<SendOrPostCallback, object>>();

    private readonly Thread thread = null;

    public ThreadSynchronizationContext()
    {
        Debug.WriteLine("------------------------");
        Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - starting worker thread sync context!");
        Debug.WriteLine("------------------------");

        // using this hack so the new thread will start running before this function returns
        using (var hack = new ManualResetEvent(false))
        {
            thread = new Thread(async obj =>
            {
                SetSynchronizationContext(obj as SynchronizationContext);

                hack.Set();

                try
                {
                    Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - awaiting queue available...");
                    while (await syncQueue.OutputAvailableAsync())
                    {
                        Debug.WriteLine("awaiting queue item...");
                        var workItem = await syncQueue.TryDequeueAsync();
                        Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - queue item received!");
                        if (workItem.Success)
                        {
                            workItem.Item.Key(workItem.Item.Value);
                        }
                    }

                    Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - queue finished :(");
                }
                catch (ObjectDisposedException e)
                {
                    Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - queue exception :((");
                }
            });
            thread.Start(this);

            hack.WaitOne();

            Debug.WriteLine("worker thread: " + WorkerThreadId);
        }
    }

    public int WorkerThreadId { get { return thread.ManagedThreadId; } }

    public void Dispose()
    {
        syncQueue.Dispose();
    }

    /// <summary>Dispatches an asynchronous message to the synchronization context.</summary>
    /// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param>
    /// <param name="state">The object passed to the delegate.</param>
    public async override void Post(SendOrPostCallback d, object state)
    {
        if (d == null) throw new ArgumentNullException("d");

        Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - enqueuing item...");
        await syncQueue.EnqueueAsync(new KeyValuePair<SendOrPostCallback, object>(d, state));
        Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - item enqueued.");
    }

    /// <summary>Dispatches a synchronous message to the synchronization context.</summary>
    /// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param>
    /// <param name="state">The object passed to the delegate.</param>
    public override void Send(SendOrPostCallback d, object state)
    {
        if (d == null) throw new ArgumentNullException("d");

        using (var handledEvent = new ManualResetEvent(false))
        {
            Post(SendOrPostCallback_BlockingWrapper, Tuple.Create(d, state, handledEvent));
            Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - waiting for blocking wrapper!");
            handledEvent.WaitOne();
            Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - blocking wrapper finished.");
        }
    }

    private static void SendOrPostCallback_BlockingWrapper(object state)
    {
        var innerCallback = (state as Tuple<SendOrPostCallback, object, ManualResetEvent>);
        try
        {
            Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - call callback from blocking wrapper...");
            innerCallback.Item1(innerCallback.Item2);
            Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - blocking wrapper callback finished.");
        }
        finally
        {
            Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - setting handle from blocking wrapper!");
            innerCallback.Item3.Set();
        }
    }
}

问题:

当我启动应用程序,并将一些代表发布到上下文时,这是输出:

------------------------
thread 1 - starting worker thread sync context!
------------------------
thread 17 - awaiting queue available...
worker thread: 17
thread 1 - enqueuing item...
thread 8 - enqueuing item...
thread 8 - item enqueued.
thread 1 - item enqueued.
thread 1 - waiting for blocking wrapper!

基本上,程序在handledEvent.WaitOne();方法的Send()行冻结,好像队列从未开始处理添加的项目。

我有点难过,任何指导都很感激。

1 个答案:

答案 0 :(得分:2)

这里的问题有点棘手,但有一个很好的线索,当你只调用{{1}时,你会看到你的“入队项......”调试输出两次 } 一次

实际发生的是自定义同步上下文由线程主委托中的Send选取。因此,它会尝试将其队列处理代码排队到自己的队列中。

要打破它:

  • 线程委托开始执行并点击await行。
  • 此时,线程委托使用当前同步上下文(await syncQueue.OutputAvailableAsync()的实例)注册其继续,然后返回(导致线程退出)。
  • 当调用代码调用ThreadSynchronizationContext时,它会将一个项目排入队列,这会导致Send完成。
  • 线程委托然后尝试通过OutputAvailableAsync继续执行捕获的Post

如果您需要单线程同步上下文,那么您根本不应该有一个异步线程委托。相反,只需使用同步API:

ThreadSynchronizationContext

事实上,我建议完全避免thread = new Thread(obj => { SetSynchronizationContext(obj as SynchronizationContext); hack.Set(); try { Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - awaiting queue available..."); while (true) { Debug.WriteLine("awaiting queue item..."); var workItem = syncQueue.TryDequeue(); Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - queue item received!"); if (!workItem.Success) break; workItem.Item.Key(workItem.Item.Value); } Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - queue finished :("); } catch (ObjectDisposedException e) { Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - queue exception :(("); } }); ,所以我建议同时使用async void同步方法(它仍然是“异步”的,因为它没有执行Post立即委托;它同步入队):

SendOrPostCallback

或者,您可以省去所有这些的痛苦,只需使用已经属于AsyncEx的the AsyncContextThread typepublic override void Post(SendOrPostCallback d, object state) { if (d == null) throw new ArgumentNullException("d"); Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - enqueuing item..."); syncQueue.Enqueue(new KeyValuePair<SendOrPostCallback, object>(d, state)); Debug.WriteLine("thread " + Thread.CurrentThread.ManagedThreadId + " - item enqueued."); } 在内部使用自己的单线程同步上下文。