From 59adf25d2a57e5d4b308ca745c916999e085509f Mon Sep 17 00:00:00 2001 From: Eugene Wang Date: Fri, 23 Nov 2018 19:10:34 -0500 Subject: [PATCH] Added UI thread sync placeholder obj & methods. --- src/NTwain/DataSource.cs | 4 +- src/NTwain/Threading/ActionItem.cs | 4 ++ src/NTwain/Threading/IThreadContext.cs | 22 ++++++ src/NTwain/Threading/UIThreadContext.cs | 95 +++++++++++++++++++++++++ src/NTwain/Threading/WinMsgLoop.cs | 30 ++++---- src/NTwain/TwainSession.MsgHandling.cs | 2 +- src/NTwain/TwainSession.Props.cs | 1 + src/NTwain/TwainSession.cs | 48 ++++++++++--- 8 files changed, 179 insertions(+), 27 deletions(-) create mode 100644 src/NTwain/Threading/IThreadContext.cs create mode 100644 src/NTwain/Threading/UIThreadContext.cs 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(); }