diff --git a/src/NTwain/DataSource.cs b/src/NTwain/DataSource.cs
index 88a2a62..038f79e 100644
--- a/src/NTwain/DataSource.cs
+++ b/src/NTwain/DataSource.cs
@@ -44,7 +44,7 @@ namespace NTwain
public ReturnCode ShowUI(IntPtr windowHandle, bool modal = false)
{
var rc = ReturnCode.Failure;
- Session.Invoke(() =>
+ Session.InternalInvoke(() =>
{
var ui = new TW_USERINTERFACE
{
@@ -72,7 +72,7 @@ namespace NTwain
public ReturnCode Enable(bool showUI, IntPtr windowHandle, bool modal = false)
{
var rc = ReturnCode.Failure;
- Session.Invoke(() =>
+ Session.InternalInvoke(() =>
{
var ui = new TW_USERINTERFACE
{
diff --git a/src/NTwain/Threading/ActionItem.cs b/src/NTwain/Threading/ActionItem.cs
index 74688a7..310bdd6 100644
--- a/src/NTwain/Threading/ActionItem.cs
+++ b/src/NTwain/Threading/ActionItem.cs
@@ -25,6 +25,10 @@ namespace NTwain.Threading
{
action();
}
+ catch (Exception ex)
+ {
+ // TODO: do something
+ }
finally
{
waiter?.Set();
diff --git a/src/NTwain/Threading/IThreadContext.cs b/src/NTwain/Threading/IThreadContext.cs
new file mode 100644
index 0000000..52b7e10
--- /dev/null
+++ b/src/NTwain/Threading/IThreadContext.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace NTwain.Threading
+{
+ ///
+ /// Interface for running some work on an associated thread.
+ ///
+ interface IThreadContext
+ {
+ ///
+ /// Runs the action synchronously on the associated thread.
+ ///
+ ///
+ void Invoke(Action action);
+
+ ///
+ /// Runs the action asynchronously on the associated thread.
+ ///
+ ///
+ void BeginInvoke(Action action);
+ }
+}
diff --git a/src/NTwain/Threading/UIThreadContext.cs b/src/NTwain/Threading/UIThreadContext.cs
new file mode 100644
index 0000000..1069dcf
--- /dev/null
+++ b/src/NTwain/Threading/UIThreadContext.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace NTwain.Threading
+{
+ ///
+ /// A using
+ /// for running actions on an UI thread.
+ ///
+ class UIThreadContext : IThreadContext
+ {
+ private readonly SynchronizationContext context;
+
+ ///
+ /// Creates a new using
+ /// the .
+ ///
+ public UIThreadContext() : this(SynchronizationContext.Current)
+ {
+
+ }
+
+ ///
+ /// Creates a new using
+ /// the specified .
+ ///
+ ///
+ public UIThreadContext(SynchronizationContext context)
+ {
+ this.context = context ?? throw new ArgumentNullException(nameof(context));
+ ;
+ }
+
+ ///
+ /// Runs the action asynchronously on the thread.
+ ///
+ ///
+ public void BeginInvoke(Action action)
+ {
+ if (action == null) return;
+
+ context.Post(o =>
+ {
+ try
+ {
+ action();
+ }
+ catch (Exception ex)
+ {
+ // TODO: do something
+ }
+ }, null);
+ }
+
+ ///
+ /// Runs the action synchronously on the thread.
+ ///
+ ///
+ public void Invoke(Action action)
+ {
+ if (action == null) return;
+
+ Exception error = null;
+ context.Send(o =>
+ {
+ try
+ {
+ action();
+ }
+ catch (Exception ex)
+ {
+ error = ex;
+ }
+ }, 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;
+ }
+ }
+}
diff --git a/src/NTwain/Threading/WinMsgLoop.cs b/src/NTwain/Threading/WinMsgLoop.cs
index 8e848ed..4176a3a 100644
--- a/src/NTwain/Threading/WinMsgLoop.cs
+++ b/src/NTwain/Threading/WinMsgLoop.cs
@@ -5,6 +5,7 @@ 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;
@@ -15,7 +16,7 @@ namespace NTwain.Threading
///
/// Provides an internal message pump on Windows using a background thread.
///
- class WinMsgLoop
+ class WinMsgLoop : IThreadContext
{
static ushort classAtom;
static IntPtr hInstance;
@@ -143,10 +144,6 @@ namespace NTwain.Threading
}
}
}
- catch
- {
- UnsafeNativeMethods.PostQuitMessage(0);
- }
finally
{
// clear queue
@@ -163,7 +160,7 @@ namespace NTwain.Threading
loopThread.Start();
startWaiter.Wait();
- if (startErr != null) throw startErr;
+ if (startErr != null) Rethrow(startErr);
}
}
@@ -193,10 +190,7 @@ namespace NTwain.Threading
return loopThread == Thread.CurrentThread || loopThread == null;
}
- ///
- /// Runs the action synchronously on the internal message pump thread.
- ///
- ///
+
public void Invoke(Action action)
{
if (IsSameThread())
@@ -216,10 +210,7 @@ namespace NTwain.Threading
}
}
- ///
- /// Runs the action asynchronously on the internal message pump thread.
- ///
- ///
+
public void BeginInvoke(Action action)
{
if (hWnd == IntPtr.Zero) action();
@@ -231,6 +222,17 @@ 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.MsgHandling.cs b/src/NTwain/TwainSession.MsgHandling.cs
index cecb483..7fec341 100644
--- a/src/NTwain/TwainSession.MsgHandling.cs
+++ b/src/NTwain/TwainSession.MsgHandling.cs
@@ -23,7 +23,7 @@ namespace NTwain
DataGroups dg, DataArgumentType dat, Message msg, IntPtr data)
{
Debug.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: {nameof(Handle32BitCallback)}({dg}, {dat}, {msg}, {data})");
- BeginInvoke(() =>
+ InternalBeginInvoke(() =>
{
Debug.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: in BeginInvoke {nameof(Handle32BitCallback)}({dg}, {dat}, {msg}, {data})");
HandleSourceMsg(msg);
diff --git a/src/NTwain/TwainSession.Props.cs b/src/NTwain/TwainSession.Props.cs
index 746ed53..d0ddd28 100644
--- a/src/NTwain/TwainSession.Props.cs
+++ b/src/NTwain/TwainSession.Props.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
+using System.Threading;
namespace NTwain
{
diff --git a/src/NTwain/TwainSession.cs b/src/NTwain/TwainSession.cs
index fc868cf..3bc8c49 100644
--- a/src/NTwain/TwainSession.cs
+++ b/src/NTwain/TwainSession.cs
@@ -20,7 +20,6 @@ namespace NTwain
{
internal readonly TwainConfig Config;
- private IntPtr _hWnd;
// cache generated twain sources so if you get same source from same session it'll return the same object
readonly Dictionary _ownedSources = new Dictionary();
// need to keep delegate around to prevent GC
@@ -28,6 +27,9 @@ namespace NTwain
// for windows only
readonly WinMsgLoop _winMsgLoop;
+ private IntPtr _hWnd;
+ private IThreadContext _extSyncContext;
+
///
/// Constructs a new .
@@ -49,27 +51,53 @@ namespace NTwain
}
///
- /// Synchronously invokes an action on the internal thread if possible.
+ /// Sets the optional synchronization context.
+ /// Because most TWAIN-related things are happening on a different thread,
+ /// this allows events to be raised on the thread associated with this context and
+ /// may be useful if you want to handle them in the UI thread.
+ ///
+ /// Usually you want to use while on the UI thread.
+ public void SetSynchronizationContext(SynchronizationContext context)
+ {
+ _extSyncContext = new UIThreadContext(context);
+ }
+
+ ///
+ /// Synchronously invokes an action on the external user thread if possible.
///
///
- ///
- public void Invoke(Action action)
+ void ExternalInvoke(Action action)
{
- if (action == null) throw new ArgumentNullException(nameof(action));
+ if (_extSyncContext != null) _extSyncContext.Invoke(action);
+ action();
+ }
+ ///
+ /// Asynchronously invokes an action on the external user thread if possible.
+ ///
+ ///
+ void ExternalBeginInvoke(Action action)
+ {
+ if (_extSyncContext != null) _extSyncContext.BeginInvoke(action);
+ action();
+ }
+
+ ///
+ /// Synchronously invokes an action on the internal TWAIN thread if possible.
+ ///
+ ///
+ internal void InternalInvoke(Action action)
+ {
if (_winMsgLoop != null) _winMsgLoop.Invoke(action);
else action();
}
///
- /// Asynchronously invokes an action on the internal thread if possible.
+ /// Asynchronously invokes an action on the internal TWAIN thread if possible.
///
///
- ///
- public void BeginInvoke(Action action)
+ void InternalBeginInvoke(Action action)
{
- if (action == null) throw new ArgumentNullException(nameof(action));
-
if (_winMsgLoop != null) _winMsgLoop.BeginInvoke(action);
else action();
}