view Implab.Diagnostics.Interactive/InteractiveListener.cs @ 187:dd4a3590f9c6 ref20160224

Reworked cancelation handling, if the cancel handler isn't specified the OperationCanceledException will be handled by the error handler Any unhandled OperationCanceledException will cause the promise cancelation
author cin
date Tue, 19 Apr 2016 17:35:20 +0300
parents 04d4c92d0f28
children cbb0bd8fc0d1
line wrap: on
line source

using Implab.Parallels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Implab.Diagnostics.Interactive
{
    public class InteractiveListener: ListenerBase
    {
        TraceForm m_form;
        
        SynchronizationContext m_syncGuiThread;
        readonly Promise m_guiStarted = new Promise();
        
        readonly IPromise m_guiFinished;
        //        readonly IPromise m_workerFinished = new Promise<object>();
        
        readonly MTQueue<TraceViewItem> m_queue = new MTQueue<TraceViewItem>();
        readonly AutoResetEvent m_queueEvent = new AutoResetEvent(false);
        
        int m_queueLength;
        bool m_exitPending;

        readonly object m_pauseLock = new object();
        bool m_paused;
        readonly ManualResetEvent m_pauseEvent = new ManualResetEvent(true);

        public InteractiveListener() {
            m_guiFinished = AsyncPool.RunThread(GuiThread);
            /*m_workerFinished = */AsyncPool.RunThread(QueueThread);

            m_guiStarted.Join();
        }

        void GuiThread() {
            m_form = new TraceForm(); // will create SynchronizationContext

            m_form.PauseEvents += (s,a) => Pause();
            m_form.ResumeEvents += (s, a) => Resume();

            m_syncGuiThread = SynchronizationContext.Current;
            m_guiStarted.Resolve();
            Application.Run();
        }

        void QueueThread() {
            while (!m_exitPending) {
                if (m_paused)
                    m_pauseEvent.WaitOne();
                
                TraceViewItem item;
                if (m_queue.TryDequeue(out item)) {
                    Interlocked.Decrement(ref m_queueLength);

                    m_syncGuiThread.Send(x => m_form.AddTraceEvent(item),null);
                } else {
                    m_queueEvent.WaitOne();
                }
            }
        }

        public void Pause() {
            // for consistency we need to set this properties atomically
            lock (m_pauseLock) {
                m_pauseEvent.Reset();
                m_paused = true;
            }
        }

        public void Resume() {
            // for consistency we need to set this properties atomically
            lock (m_pauseLock) {
                m_paused = false;
                m_pauseEvent.Set();
            }
        }

        void Enqueue(TraceViewItem item) {
            m_queue.Enqueue(item);
            if (Interlocked.Increment(ref m_queueLength) == 1)
                m_queueEvent.Set();
        }

        public void ShowForm() {
            m_syncGuiThread.Post(x => m_form.Show(), null);
        }

        public void HideForm() {
            m_syncGuiThread.Post(x => m_form.Hide(), null);
        }

        void Terminate() {
            m_exitPending = true;
            Resume();            
            m_syncGuiThread.Post(x => Application.ExitThread(), null);
        }

        protected override void Dispose(bool disposing) {
            if (disposing) {
                Terminate();
                m_guiFinished.Join();
            }
            base.Dispose(disposing);
        }

        public override void Write(LogEventArgs args, object entry) {
            var item = new TraceViewItem {
                Indent = args.Operation.Level,
                Message = entry.ToString(),
                Thread = args.ThreadId,
                Channel = args.ChannelName,
                Timestamp = Environment.TickCount
            };

            Enqueue(item);
        }
    }
}