diff --git a/Implab.Fx.Test/Implab.Fx.Test.csproj b/Implab.Fx.Test/Implab.Fx.Test.csproj --- a/Implab.Fx.Test/Implab.Fx.Test.csproj +++ b/Implab.Fx.Test/Implab.Fx.Test.csproj @@ -80,6 +80,7 @@ OverlayForm.cs + diff --git a/Implab.Fx.Test/StaApartmentTests.cs b/Implab.Fx.Test/StaApartmentTests.cs new file mode 100644 --- /dev/null +++ b/Implab.Fx.Test/StaApartmentTests.cs @@ -0,0 +1,52 @@ +using System; +using System.Reflection; +using System.Threading; +using Implab.Parallels; +using Implab.Components; + +#if MONO + +using NUnit.Framework; +using TestClassAttribute = NUnit.Framework.TestFixtureAttribute; +using TestMethodAttribute = NUnit.Framework.TestAttribute; +using AssertFailedException = NUnit.Framework.AssertionException; +#else + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +#endif +namespace Implab.Fx.Test { + [TestClass] + public class StaApartmentTests { + [TestMethod] + public void CreateDestroyApartment() { + var apartment = new StaApartment(); + try { + Assert.IsNotNull(apartment.SyncContext); + Assert.Fail(); + } catch (InvalidOperationException) { + // OK + } + + apartment.Start().Join(); + Assert.AreEqual(apartment.State, ExecutionState.Running); + + Assert.IsNotNull(apartment.SyncContext); + apartment.Stop().Join(); + + Assert.IsTrue(apartment.State == ExecutionState.Disposed); + } + + [TestMethod] + public void InvokeInApartment() { + var apartment = new StaApartment(); + + apartment.Start().Join(); + + var apType = apartment.Invoke(() => { return Thread.CurrentThread.GetApartmentState(); }).Join(); + Assert.AreEqual(apType, ApartmentState.STA); + + apartment.Stop().Join(); + } + } +} diff --git a/Implab.Fx/Implab.Fx.csproj b/Implab.Fx/Implab.Fx.csproj --- a/Implab.Fx/Implab.Fx.csproj +++ b/Implab.Fx/Implab.Fx.csproj @@ -70,6 +70,7 @@ + diff --git a/Implab.Fx/StaApartment.cs b/Implab.Fx/StaApartment.cs new file mode 100644 --- /dev/null +++ b/Implab.Fx/StaApartment.cs @@ -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 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 Invoke(Func 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; + 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 Invoke(Func 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; + p.Resolve(action()); + } catch (Exception e) { + p.Reject(e); + } finally { + TraceContext.Instance.Leave(); + } + }, null); + + return p; + } + + + /// + /// Starts the apartment thread + /// + /// Promise which will be fullfiled when the syncronization + /// context will be ready to accept tasks. + protected override IPromise OnStart() { + m_worker.Start(); + return m_threadStarted; + } + + /// + /// Posts quit message to the message loop of the apartment + /// + /// Promise + 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); + } + } + + /// + /// Called from the STA apartment after the message loop is terminated, override this + /// method to handle apartment cleanup. + /// + 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); + } + } +} diff --git a/Implab.Test/Implab.Test.csproj b/Implab.Test/Implab.Test.csproj --- a/Implab.Test/Implab.Test.csproj +++ b/Implab.Test/Implab.Test.csproj @@ -63,6 +63,9 @@ + + + @@ -73,6 +76,9 @@ Implab + + +