Added UI thread sync placeholder obj & methods.

This commit is contained in:
Eugene Wang
2018-11-23 19:10:34 -05:00
parent b489a3ef58
commit 59adf25d2a
8 changed files with 179 additions and 27 deletions

View File

@@ -44,7 +44,7 @@ namespace NTwain
public ReturnCode ShowUI(IntPtr windowHandle, bool modal = false) public ReturnCode ShowUI(IntPtr windowHandle, bool modal = false)
{ {
var rc = ReturnCode.Failure; var rc = ReturnCode.Failure;
Session.Invoke(() => Session.InternalInvoke(() =>
{ {
var ui = new TW_USERINTERFACE var ui = new TW_USERINTERFACE
{ {
@@ -72,7 +72,7 @@ namespace NTwain
public ReturnCode Enable(bool showUI, IntPtr windowHandle, bool modal = false) public ReturnCode Enable(bool showUI, IntPtr windowHandle, bool modal = false)
{ {
var rc = ReturnCode.Failure; var rc = ReturnCode.Failure;
Session.Invoke(() => Session.InternalInvoke(() =>
{ {
var ui = new TW_USERINTERFACE var ui = new TW_USERINTERFACE
{ {

View File

@@ -25,6 +25,10 @@ namespace NTwain.Threading
{ {
action(); action();
} }
catch (Exception ex)
{
// TODO: do something
}
finally finally
{ {
waiter?.Set(); waiter?.Set();

View File

@@ -0,0 +1,22 @@
using System;
namespace NTwain.Threading
{
/// <summary>
/// Interface for running some work on an associated thread.
/// </summary>
interface IThreadContext
{
/// <summary>
/// Runs the action synchronously on the associated thread.
/// </summary>
/// <param name="action"></param>
void Invoke(Action action);
/// <summary>
/// Runs the action asynchronously on the associated thread.
/// </summary>
/// <param name="action"></param>
void BeginInvoke(Action action);
}
}

View File

@@ -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
{
/// <summary>
/// A <see cref="IThreadContext"/> using <see cref="SynchronizationContext"/>
/// for running actions on an UI thread.
/// </summary>
class UIThreadContext : IThreadContext
{
private readonly SynchronizationContext context;
/// <summary>
/// Creates a new <see cref="UIThreadContext"/> using
/// the <see cref="SynchronizationContext.Current"/>.
/// </summary>
public UIThreadContext() : this(SynchronizationContext.Current)
{
}
/// <summary>
/// Creates a new <see cref="UIThreadContext"/> using
/// the specified <see cref="SynchronizationContext"/>.
/// </summary>
/// <param name="context"></param>
public UIThreadContext(SynchronizationContext context)
{
this.context = context ?? throw new ArgumentNullException(nameof(context));
;
}
/// <summary>
/// Runs the action asynchronously on the <see cref="SynchronizationContext"/> thread.
/// </summary>
/// <param name="action"></param>
public void BeginInvoke(Action action)
{
if (action == null) return;
context.Post(o =>
{
try
{
action();
}
catch (Exception ex)
{
// TODO: do something
}
}, null);
}
/// <summary>
/// Runs the action synchronously on the <see cref="SynchronizationContext"/> thread.
/// </summary>
/// <param name="action"></param>
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); }
}
/// <summary>
/// Rethrows the specified excetion while keeping stack trace.
/// </summary>
/// <param name="ex">The ex.</param>
static void Rethrow(Exception ex)
{
typeof(Exception).GetMethod("PrepForRemoting",
BindingFlags.NonPublic | BindingFlags.Instance)?.Invoke(ex, new object[0]);
throw ex;
}
}
}

View File

@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security; using System.Security;
using System.Text; using System.Text;
@@ -15,7 +16,7 @@ namespace NTwain.Threading
/// <summary> /// <summary>
/// Provides an internal message pump on Windows using a background thread. /// Provides an internal message pump on Windows using a background thread.
/// </summary> /// </summary>
class WinMsgLoop class WinMsgLoop : IThreadContext
{ {
static ushort classAtom; static ushort classAtom;
static IntPtr hInstance; static IntPtr hInstance;
@@ -143,10 +144,6 @@ namespace NTwain.Threading
} }
} }
} }
catch
{
UnsafeNativeMethods.PostQuitMessage(0);
}
finally finally
{ {
// clear queue // clear queue
@@ -163,7 +160,7 @@ namespace NTwain.Threading
loopThread.Start(); loopThread.Start();
startWaiter.Wait(); 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; return loopThread == Thread.CurrentThread || loopThread == null;
} }
/// <summary>
/// Runs the action synchronously on the internal message pump thread.
/// </summary>
/// <param name="action"></param>
public void Invoke(Action action) public void Invoke(Action action)
{ {
if (IsSameThread()) if (IsSameThread())
@@ -216,10 +210,7 @@ namespace NTwain.Threading
} }
} }
/// <summary>
/// Runs the action asynchronously on the internal message pump thread.
/// </summary>
/// <param name="action"></param>
public void BeginInvoke(Action action) public void BeginInvoke(Action action)
{ {
if (hWnd == IntPtr.Zero) action(); if (hWnd == IntPtr.Zero) action();
@@ -231,6 +222,17 @@ namespace NTwain.Threading
} }
/// <summary>
/// Rethrows the specified excetion while keeping stack trace.
/// </summary>
/// <param name="ex">The ex.</param>
static void Rethrow(Exception ex)
{
typeof(Exception).GetMethod("PrepForRemoting",
BindingFlags.NonPublic | BindingFlags.Instance)?.Invoke(ex, new object[0]);
throw ex;
}
[SuppressUnmanagedCodeSecurity] [SuppressUnmanagedCodeSecurity]
internal static class UnsafeNativeMethods internal static class UnsafeNativeMethods
{ {

View File

@@ -23,7 +23,7 @@ namespace NTwain
DataGroups dg, DataArgumentType dat, Message msg, IntPtr data) DataGroups dg, DataArgumentType dat, Message msg, IntPtr data)
{ {
Debug.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: {nameof(Handle32BitCallback)}({dg}, {dat}, {msg}, {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})"); Debug.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: in BeginInvoke {nameof(Handle32BitCallback)}({dg}, {dat}, {msg}, {data})");
HandleSourceMsg(msg); HandleSourceMsg(msg);

View File

@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading;
namespace NTwain namespace NTwain
{ {

View File

@@ -20,7 +20,6 @@ namespace NTwain
{ {
internal readonly TwainConfig Config; 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 // cache generated twain sources so if you get same source from same session it'll return the same object
readonly Dictionary<string, DataSource> _ownedSources = new Dictionary<string, DataSource>(); readonly Dictionary<string, DataSource> _ownedSources = new Dictionary<string, DataSource>();
// need to keep delegate around to prevent GC // need to keep delegate around to prevent GC
@@ -28,6 +27,9 @@ namespace NTwain
// for windows only // for windows only
readonly WinMsgLoop _winMsgLoop; readonly WinMsgLoop _winMsgLoop;
private IntPtr _hWnd;
private IThreadContext _extSyncContext;
/// <summary> /// <summary>
/// Constructs a new <see cref="TwainSession"/>. /// Constructs a new <see cref="TwainSession"/>.
@@ -49,27 +51,53 @@ namespace NTwain
} }
/// <summary> /// <summary>
/// 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.
/// </summary>
/// <param name="context">Usually you want to use <see cref="SynchronizationContext.Current"/> while on the UI thread.</param>
public void SetSynchronizationContext(SynchronizationContext context)
{
_extSyncContext = new UIThreadContext(context);
}
/// <summary>
/// Synchronously invokes an action on the external user thread if possible.
/// </summary> /// </summary>
/// <param name="action"></param> /// <param name="action"></param>
/// <exception cref="ArgumentNullException"></exception> void ExternalInvoke(Action action)
public void Invoke(Action action)
{ {
if (action == null) throw new ArgumentNullException(nameof(action)); if (_extSyncContext != null) _extSyncContext.Invoke(action);
action();
}
/// <summary>
/// Asynchronously invokes an action on the external user thread if possible.
/// </summary>
/// <param name="action"></param>
void ExternalBeginInvoke(Action action)
{
if (_extSyncContext != null) _extSyncContext.BeginInvoke(action);
action();
}
/// <summary>
/// Synchronously invokes an action on the internal TWAIN thread if possible.
/// </summary>
/// <param name="action"></param>
internal void InternalInvoke(Action action)
{
if (_winMsgLoop != null) _winMsgLoop.Invoke(action); if (_winMsgLoop != null) _winMsgLoop.Invoke(action);
else action(); else action();
} }
/// <summary> /// <summary>
/// Asynchronously invokes an action on the internal thread if possible. /// Asynchronously invokes an action on the internal TWAIN thread if possible.
/// </summary> /// </summary>
/// <param name="action"></param> /// <param name="action"></param>
/// <exception cref="ArgumentNullException"></exception> void InternalBeginInvoke(Action action)
public void BeginInvoke(Action action)
{ {
if (action == null) throw new ArgumentNullException(nameof(action));
if (_winMsgLoop != null) _winMsgLoop.BeginInvoke(action); if (_winMsgLoop != null) _winMsgLoop.BeginInvoke(action);
else action(); else action();
} }