view Implab.Fx/StaApartment.cs @ 238:bdfdba6b645b v2

fixed unpredictable Safe.Dispose behaviour
author cin
date Fri, 01 Dec 2017 01:28:56 +0300
parents 3eb3255d8cc5
children cbe10ac0731e
line wrap: on
line source

using Implab.Components;
using Implab.Diagnostics;
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.Fx {
    public class StaApartment : RunnableComponent {
        readonly Thread m_worker;
        SynchronizationContext m_syncContext;
        SyncContextPromise m_enterPromise;

        readonly Promise m_threadStarted;
        readonly Promise m_threadTerminated;

        public StaApartment() : base(true) {
            m_threadStarted = new Promise();
            m_threadTerminated = new Promise();

            m_worker = new Thread(WorkerEntry);
            m_worker.SetApartmentState(ApartmentState.STA);
            m_worker.IsBackground = true;
            m_worker.Name = "STA managed aparment";
        }

        public SynchronizationContext SyncContext {
            get {
                if (m_syncContext == null)
                    throw new InvalidOperationException();
                return m_syncContext;
            }
        }

        /// <summary>
        /// Returns the promise which will dispatch all handlers inside the apartment using it's <see cref="SynchronizationContext"/>
        /// </summary>
        /// <remarks>
        /// Current implementation is optimized and will lost aync operation stack
        /// </remarks>
        /// <returns>The promise</returns>
        public IPromise Enter() {
            if (m_enterPromise == null)
                throw new InvalidOperationException();
            return m_enterPromise;
        }

        public IPromise Invoke(Action<ICancellationToken> action) {
            Safe.ArgumentNotNull(action, "action");

            if (m_syncContext == null)
                throw new InvalidOperationException();
            var p = new Promise();
            var lop = TraceContext.Instance.CurrentOperation;

            m_syncContext.Post(x => {
                TraceContext.Instance.EnterLogicalOperation(lop, false);
                try {
                    if (p.CancelOperationIfRequested())
                        return;

                    action(p);
                    p.Resolve();
                } catch (Exception e) {
                    p.Reject(e);
                } finally {
                    TraceContext.Instance.Leave();
                }
            }, null);

            return p;
        }

        public IPromise<T> Invoke<T>(Func<ICancellationToken, T> action) {
            Safe.ArgumentNotNull(action, "action");

            if (m_syncContext == null)
                throw new InvalidOperationException();
            var p = new Promise<T>();
            var lop = TraceContext.Instance.CurrentOperation;

            m_syncContext.Post(x => {
                TraceContext.Instance.EnterLogicalOperation(lop, false);
                try {
                    if (p.CancelOperationIfRequested())
                        return;
                    p.Resolve(action(p));
                } catch (Exception e) {
                    p.Reject(e);
                } finally {
                    TraceContext.Instance.Leave();
                }
            }, null);

            return p;
        }

        public IPromise Invoke(Action action) {
            Safe.ArgumentNotNull(action, "action");

            if (m_syncContext == null)
                throw new InvalidOperationException();
            var p = new Promise();
            var lop = TraceContext.Instance.CurrentOperation;

            m_syncContext.Post(x => {
                TraceContext.Instance.EnterLogicalOperation(lop, false);
                try {
                    if (p.CancelOperationIfRequested())
                        return;
                    action();
                    p.Resolve();
                } catch (Exception e) {
                    p.Reject(e);
                } finally {
                    TraceContext.Instance.Leave();
                }
            }, null);

            return p;
        }

        public IPromise<T> Invoke<T>(Func<T> action) {
            Safe.ArgumentNotNull(action, "action");

            if (m_syncContext == null)
                throw new InvalidOperationException();
            var p = new Promise<T>();
            var lop = TraceContext.Instance.CurrentOperation;

            m_syncContext.Post(x => {
                TraceContext.Instance.EnterLogicalOperation(lop, false);
                try {
                    if (p.CancelOperationIfRequested())
                        return;
                    p.Resolve(action());
                } catch (Exception e) {
                    p.Reject(e);
                } finally {
                    TraceContext.Instance.Leave();
                }
            }, null);

            return p;
        }


        /// <summary>
        /// Starts the apartment thread
        /// </summary>
        /// <returns>Promise which will be fullfiled when the syncronization
        /// context will be ready to accept tasks.</returns>
        protected override IPromise OnStart() {
            m_worker.Start();
            return m_threadStarted;
        }

        /// <summary>
        /// Posts quit message to the message loop of the apartment
        /// </summary>
        /// <returns>Promise</returns>
        protected override IPromise OnStop() {
            m_syncContext.Post(x => Application.ExitThread(), null);
            return m_threadTerminated;
        }

        void WorkerEntry() {
            m_syncContext = new WindowsFormsSynchronizationContext();
            SynchronizationContext.SetSynchronizationContext(m_syncContext);
            m_enterPromise = new SyncContextPromise(m_syncContext);
            m_threadStarted.Resolve();
            m_enterPromise.Resolve();

            Application.OleRequired();
            Application.Run();

            try {
                OnShutdown();
                m_threadTerminated.Resolve();
            } catch(Exception err) {
                m_threadTerminated.Reject(err);
            }
        }

        /// <summary>
        /// Called from the STA apartment after the message loop is terminated, override this
        /// method to handle apartment cleanup.
        /// </summary>
        protected virtual void OnShutdown() {
        }

        protected override void Dispose(bool disposing) {
            if (disposing) {
                if (!m_threadTerminated.IsResolved)
                    m_syncContext.Post(x => Application.ExitThread(), null);
            }
            base.Dispose(disposing);
        }
    }
}