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
+
+
+