|
|
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);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|