diff Implab.Fx/StaApartment.cs @ 210:5dc21f6a3222 v2

Code review for RunnableComponent Added StaApartment class based on System.Windows.Forms.Application message loop
author cin
date Mon, 20 Mar 2017 17:44:18 +0300
parents
children 3eb3255d8cc5
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab.Fx/StaApartment.cs	Mon Mar 20 17:44:18 2017 +0300
@@ -0,0 +1,188 @@
+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;
+        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;
+            }
+        }
+
+        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_threadStarted.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);
+        }
+    }
+}