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)
{
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
{

View File

@@ -25,6 +25,10 @@ namespace NTwain.Threading
{
action();
}
catch (Exception ex)
{
// TODO: do something
}
finally
{
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.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
@@ -15,7 +16,7 @@ namespace NTwain.Threading
/// <summary>
/// Provides an internal message pump on Windows using a background thread.
/// </summary>
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;
}
/// <summary>
/// Runs the action synchronously on the internal message pump thread.
/// </summary>
/// <param name="action"></param>
public void Invoke(Action action)
{
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)
{
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]
internal static class UnsafeNativeMethods
{

View File

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

View File

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

View File

@@ -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<string, DataSource> _ownedSources = new Dictionary<string, DataSource>();
// 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;
/// <summary>
/// Constructs a new <see cref="TwainSession"/>.
@@ -49,27 +51,53 @@ namespace NTwain
}
/// <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>
/// <param name="action"></param>
/// <exception cref="ArgumentNullException"></exception>
public void Invoke(Action action)
void ExternalInvoke(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);
else action();
}
/// <summary>
/// Asynchronously invokes an action on the internal thread if possible.
/// Asynchronously invokes an action on the internal TWAIN thread if possible.
/// </summary>
/// <param name="action"></param>
/// <exception cref="ArgumentNullException"></exception>
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();
}