view Implab.Diagnostics.Interactive/InteractiveListener.cs @ 238:bdfdba6b645b v2

fixed unpredictable Safe.Dispose behaviour
author cin
date Fri, 01 Dec 2017 01:28:56 +0300
parents fe5101083150
children eedf4d834e67
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 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 = RunGuiThread();
            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.Post(x => m_form.AddTraceEvent(item),null);
                } else {
                    m_queueEvent.WaitOne();
                }
            }
        }

        public IPromise RunGuiThread() {
            var p = new Promise();

            var caller = TraceContext.Instance.CurrentOperation;

            var worker = new Thread(() => {
                TraceContext.Instance.EnterLogicalOperation(caller, false);
                try {
                    Application.OleRequired();
                    GuiThread();
                    p.Resolve();
                } catch (Exception e) {
                    p.Reject(e);
                } finally {
                    TraceContext.Instance.Leave();
                }
            });
            worker.SetApartmentState(ApartmentState.STA);
            worker.IsBackground = true;
            worker.Name = string.Format("{0} GUI Thread", nameof(InteractiveListener));
            worker.Start();

            return p;
        }

        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.Channel.ToString(),
                Timestamp = Environment.TickCount,
                TimeDelta = args.OperationTimeOffset
            };

            Enqueue(item);
        }
    }
}