Added first naive dispatcher loop without win message pump.

This commit is contained in:
Eugene Wang 2018-11-23 19:59:37 -05:00
parent 47ae473316
commit 9d7b1154f5
6 changed files with 151 additions and 27 deletions

View File

@ -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
{
/// <summary>
/// Rethrows the specified excetion while keeping stack trace
/// if not null.
/// </summary>
/// <param name="ex">The ex.</param>
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;
}
}
}
}

View File

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

View File

@ -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
{
/// <summary>
/// Provides an internal message pump on non-Windows using a background thread.
/// </summary>
class DispatcherLoop : IThreadContext
{
readonly TwainSession session;
BlockingCollection<ActionItem> 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<ActionItem>();
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));
}
}
}

View File

@ -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); }
}
/// <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;
error.TryRethrow();
}
void IThreadContext.Start()

View File

@ -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
}
/// <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

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