diff --git a/src/NTwain/Internals/ExceptionExtensions.cs b/src/NTwain/Internals/ExceptionExtensions.cs
new file mode 100644
index 0000000..f186b38
--- /dev/null
+++ b/src/NTwain/Internals/ExceptionExtensions.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NTwain.Internals
+{
+ static class ExceptionExtensions
+ {
+ ///
+ /// Rethrows the specified excetion while keeping stack trace
+ /// if not null.
+ ///
+ /// The ex.
+ public static void TryRethrow(this Exception ex)
+ {
+ if (ex != null)
+ {
+ typeof(Exception).GetMethod("PrepForRemoting",
+ BindingFlags.NonPublic | BindingFlags.Instance)?.Invoke(ex, new object[0]);
+ throw ex;
+ }
+ }
+
+ }
+}
diff --git a/src/NTwain/Threading/ActionItem.cs b/src/NTwain/Threading/ActionItem.cs
index 310bdd6..32a947f 100644
--- a/src/NTwain/Threading/ActionItem.cs
+++ b/src/NTwain/Threading/ActionItem.cs
@@ -8,6 +8,8 @@ namespace NTwain.Threading
private ManualResetEventSlim waiter;
private Action action;
+ public Exception Error { get; private set; }
+
public ActionItem(Action action)
{
this.action = action;
@@ -27,7 +29,7 @@ namespace NTwain.Threading
}
catch (Exception ex)
{
- // TODO: do something
+ Error = ex;
}
finally
{
diff --git a/src/NTwain/Threading/DispatcherLoop.cs b/src/NTwain/Threading/DispatcherLoop.cs
new file mode 100644
index 0000000..68e7e75
--- /dev/null
+++ b/src/NTwain/Threading/DispatcherLoop.cs
@@ -0,0 +1,110 @@
+using NTwain.Data.Win32;
+using NTwain.Internals;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Text;
+using System.Threading;
+
+namespace NTwain.Threading
+{
+ ///
+ /// Provides an internal message pump on non-Windows using a background thread.
+ ///
+ class DispatcherLoop : IThreadContext
+ {
+ readonly TwainSession session;
+ BlockingCollection actionQueue;
+ CancellationTokenSource stopToken;
+
+ Thread loopThread;
+
+ public DispatcherLoop(TwainSession session)
+ {
+ this.session = session;
+ }
+
+ public void Start()
+ {
+ if (loopThread != null) return;
+
+ Debug.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: {nameof(WinMsgLoop)}.{nameof(Start)}()");
+
+ actionQueue = new BlockingCollection();
+ stopToken = new CancellationTokenSource();
+
+ // startWaiter ensures thread is running before this method returns
+ using (var startWaiter = new ManualResetEventSlim())
+ {
+ loopThread = new Thread(new ThreadStart(() =>
+ {
+ Debug.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: starting msg pump...");
+
+ startWaiter.Set();
+
+ try
+ {
+ while (actionQueue.TryTake(out ActionItem work, -1, stopToken.Token))
+ {
+ work.DoAction();
+ }
+ }
+ catch (OperationCanceledException) { }
+ finally
+ {
+ // clear queue
+ actionQueue.Dispose();
+
+ loopThread = null;
+ }
+ }));
+ loopThread.IsBackground = true;
+ loopThread.TrySetApartmentState(ApartmentState.STA);
+ loopThread.Start();
+ startWaiter.Wait();
+ }
+ }
+
+ public void Stop()
+ {
+ if (!stopToken.IsCancellationRequested) stopToken.Cancel();
+ }
+
+ bool IsSameThread()
+ {
+ return loopThread == Thread.CurrentThread || loopThread == null;
+ }
+
+
+ public void Invoke(Action action)
+ {
+ if (IsSameThread())
+ {
+ // ok
+ action();
+ }
+ else
+ {
+ // queue up work
+ using (var waiter = new ManualResetEventSlim())
+ {
+ var work = new ActionItem(waiter, action);
+ actionQueue.TryAdd(work);
+ waiter.Wait();
+ work.Error.TryRethrow();
+ }
+ }
+ }
+
+ public void BeginInvoke(Action action)
+ {
+ actionQueue.TryAdd(new ActionItem(action));
+ }
+ }
+}
diff --git a/src/NTwain/Threading/UIThreadContext.cs b/src/NTwain/Threading/UIThreadContext.cs
index c58b19f..770b186 100644
--- a/src/NTwain/Threading/UIThreadContext.cs
+++ b/src/NTwain/Threading/UIThreadContext.cs
@@ -1,4 +1,5 @@
-using System;
+using NTwain.Internals;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@@ -68,18 +69,7 @@ namespace NTwain.Threading
}
}, null);
- if (error != null) { Rethrow(error); }
- }
-
- ///
- /// Rethrows the specified excetion while keeping stack trace.
- ///
- /// The ex.
- static void Rethrow(Exception ex)
- {
- typeof(Exception).GetMethod("PrepForRemoting",
- BindingFlags.NonPublic | BindingFlags.Instance)?.Invoke(ex, new object[0]);
- throw ex;
+ error.TryRethrow();
}
void IThreadContext.Start()
diff --git a/src/NTwain/Threading/WinMsgLoop.cs b/src/NTwain/Threading/WinMsgLoop.cs
index 4176a3a..36992a4 100644
--- a/src/NTwain/Threading/WinMsgLoop.cs
+++ b/src/NTwain/Threading/WinMsgLoop.cs
@@ -1,4 +1,5 @@
using NTwain.Data.Win32;
+using NTwain.Internals;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -160,7 +161,7 @@ namespace NTwain.Threading
loopThread.Start();
startWaiter.Wait();
- if (startErr != null) Rethrow(startErr);
+ startErr.TryRethrow();
}
}
@@ -203,9 +204,11 @@ namespace NTwain.Threading
// queue up work
using (var waiter = new ManualResetEventSlim())
{
- actionQueue.Enqueue(new ActionItem(waiter, action));
+ var work = new ActionItem(waiter, action);
+ actionQueue.Enqueue(work);
UnsafeNativeMethods.PostMessageW(hWnd, dequeMsg, IntPtr.Zero, IntPtr.Zero);
waiter.Wait();
+ work.Error.TryRethrow();
}
}
}
@@ -222,17 +225,6 @@ namespace NTwain.Threading
}
- ///
- /// Rethrows the specified excetion while keeping stack trace.
- ///
- /// The ex.
- static void Rethrow(Exception ex)
- {
- typeof(Exception).GetMethod("PrepForRemoting",
- BindingFlags.NonPublic | BindingFlags.Instance)?.Invoke(ex, new object[0]);
- throw ex;
- }
-
[SuppressUnmanagedCodeSecurity]
internal static class UnsafeNativeMethods
{
diff --git a/src/NTwain/TwainSession.cs b/src/NTwain/TwainSession.cs
index 6fed785..bb06e02 100644
--- a/src/NTwain/TwainSession.cs
+++ b/src/NTwain/TwainSession.cs
@@ -44,6 +44,8 @@ namespace NTwain
{
case PlatformID.MacOSX:
case PlatformID.Unix:
+ _internalContext = new DispatcherLoop(this);
+ break;
default:
_internalContext = new WinMsgLoop(this);
_callback32Delegate = new Callback32(Handle32BitCallback);