Changed to use SynchronizationContext instead of the DIY threadmarshaller.

This commit is contained in:
Eugene Wang
2023-04-10 08:02:38 -04:00
parent 4b337f036e
commit 9cd20f3e62
8 changed files with 39 additions and 147 deletions

View File

@@ -11,6 +11,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
namespace WinFormSample namespace WinFormSample
@@ -28,7 +29,7 @@ namespace WinFormSample
TWPlatform.PreferLegacyDSM = false; TWPlatform.PreferLegacyDSM = false;
twain = new TwainAppSession(new WinformMarshaller(this), Assembly.GetExecutingAssembly().Location); twain = new TwainAppSession(SynchronizationContext.Current!, Assembly.GetExecutingAssembly().Location);
twain.StateChanged += Twain_StateChanged; twain.StateChanged += Twain_StateChanged;
twain.DefaultSourceChanged += Twain_DefaultSourceChanged; twain.DefaultSourceChanged += Twain_DefaultSourceChanged;
twain.CurrentSourceChanged += Twain_CurrentSourceChanged; twain.CurrentSourceChanged += Twain_CurrentSourceChanged;

View File

@@ -1,58 +0,0 @@
#if WINDOWS || NETFRAMEWORK
using System;
using System.Windows.Forms;
using System.Windows.Threading;
namespace NTwain
{
/// <summary>
/// An <see cref="IThreadMarshaller"/> that can be used
/// to integrate <see cref="TwainAppSession"/> with
/// an existing Winforms app.
/// </summary>
public class WinformMarshaller : IThreadMarshaller
{
private readonly Control control;
public WinformMarshaller(System.Windows.Forms.Control control)
{
this.control = control;
}
public void BeginInvoke(Delegate work, params object[] args)
{
control.BeginInvoke(work, args);
}
public object? Invoke(Delegate work, params object[] args)
{
return control.Invoke(work, args);
}
}
/// <summary>
/// An <see cref="IThreadMarshaller"/> that can be used
/// to integrate <see cref="TwainAppSession"/> with
/// an existing WPF app.
/// </summary>
public class WpfMarshaller : IThreadMarshaller
{
private readonly Dispatcher dispatcher;
public WpfMarshaller(Dispatcher dispatcher)
{
this.dispatcher = dispatcher;
}
public void BeginInvoke(Delegate work, params object[] args)
{
dispatcher.BeginInvoke(work, args);
}
public object? Invoke(Delegate work, params object[] args)
{
return dispatcher.Invoke(work, args);
}
}
}
#endif

View File

@@ -1,46 +0,0 @@
using System;
namespace NTwain
{
/// <summary>
/// Allows work to be marshalled to a different (usually UI) thread as necessary.
/// </summary>
public interface IThreadMarshaller
{
/// <summary>
/// Starts work asynchronously and returns immediately.
/// </summary>
/// <param name="work"></param>
/// <param name="args"></param>
void BeginInvoke(Delegate work, params object[] args);
/// <summary>
/// Starts work synchronously until it returns.
/// </summary>
/// <param name="work"></param>
/// <param name="args"></param>
/// <returns></returns>
object? Invoke(Delegate work, params object[] args);
}
/// <summary>
/// No marshalling occurs. Invokes happen right in place synchronously.
/// </summary>
public class InPlaceMarshaller : IThreadMarshaller
{
/// <summary>
/// No async invocation here.
/// </summary>
/// <param name="work"></param>
/// <param name="args"></param>
public void BeginInvoke(Delegate work, params object[] args)
{
work.DynamicInvoke(args);
}
public object? Invoke(Delegate work, params object[] args)
{
return work.DynamicInvoke(args);
}
}
}

View File

@@ -112,23 +112,24 @@ namespace NTwain
if (!_inTransfer) if (!_inTransfer)
{ {
// this should be done on ui thread (or same one that enabled the ds) // this should be done on ui thread (or same one that enabled the ds)
_uiThreadMarshaller.BeginInvoke(() => _uiThreadMarshaller.Post(obj =>
{ {
DisableSource(); ((TwainAppSession)obj!).DisableSource();
}); }, this);
} }
break; break;
case MSG.DEVICEEVENT: case MSG.DEVICEEVENT:
if (DeviceEvent != null && DGControl.DeviceEvent.Get(ref _appIdentity, ref _currentDS, out TW_DEVICEEVENT de) == TWRC.SUCCESS) if (DeviceEvent != null && DGControl.DeviceEvent.Get(ref _appIdentity, ref _currentDS, out TW_DEVICEEVENT de) == TWRC.SUCCESS)
{ {
_uiThreadMarshaller.BeginInvoke(() => _uiThreadMarshaller.Post(obj =>
{ {
try try
{ {
DeviceEvent.Invoke(this, de); var twain = (TwainAppSession)obj!;
twain.DeviceEvent!.Invoke(twain, de);
} }
catch { } catch { }
}); }, this);
} }
break; break;
} }

View File

@@ -57,17 +57,14 @@ namespace NTwain
if (_state != value) if (_state != value)
{ {
_state = value; _state = value;
if (StateChanged != null) _uiThreadMarshaller.Send(obj =>
{ {
_uiThreadMarshaller.Invoke(() => try
{ {
try ((TwainAppSession)obj!).StateChanged?.Invoke(this, value);
{ }
StateChanged.Invoke(this, value); catch { }
} }, this);
catch { }
});
}
} }
} }
} }

View File

@@ -60,16 +60,16 @@ namespace NTwain
bool IMessageFilter.PreFilterMessage(ref Message m) bool IMessageFilter.PreFilterMessage(ref Message m)
{ {
return CheckIfTwainMessage(m.HWnd, m.Msg, m.WParam, m.LParam); return WndProc(m.HWnd, m.Msg, m.WParam, m.LParam);
} }
IntPtr WpfHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) IntPtr WpfHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{ {
handled = CheckIfTwainMessage(hwnd, msg, wParam, lParam); handled = WndProc(hwnd, msg, wParam, lParam);
return IntPtr.Zero; return IntPtr.Zero;
} }
private bool CheckIfTwainMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam) private bool WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam)
{ {
// this handles the message from a typical WndProc message loop and checks if it's for the TWAIN source. // this handles the message from a typical WndProc message loop and checks if it's for the TWAIN source.
bool handled = false; bool handled = false;

View File

@@ -74,14 +74,15 @@ namespace NTwain
var readyArgs = new TransferReadyEventArgs(pending.Count, (TWEJ)pending.EOJ); var readyArgs = new TransferReadyEventArgs(pending.Count, (TWEJ)pending.EOJ);
if (TransferReady != null) if (TransferReady != null)
{ {
_uiThreadMarshaller.Invoke(() => _uiThreadMarshaller.Send(_ =>
{ {
try try
{ {
TransferReady.Invoke(this, readyArgs); TransferReady.Invoke(this, readyArgs);
} }
catch { } // don't let consumer kill the loop if they have exception catch { } // don't let consumer kill the loop if they have exception
}); }, null);
} }
if (readyArgs.Cancel == CancelType.EndNow || _closeDsRequested) if (readyArgs.Cancel == CancelType.EndNow || _closeDsRequested)
@@ -154,17 +155,15 @@ namespace NTwain
} }
catch (Exception ex) catch (Exception ex)
{ {
if (TransferError != null) _uiThreadMarshaller.Send(obj =>
{ {
_uiThreadMarshaller.Invoke(() => try
{ {
try var twain = ((TwainAppSession)obj!);
{ twain.TransferError?.Invoke(twain, new TransferErrorEventArgs(ex));
TransferError?.Invoke(this, new TransferErrorEventArgs(ex)); }
} catch { }
catch { } }, this);
});
}
} }
} }
} while (sts.RC == TWRC.SUCCESS && pending.Count != 0); } while (sts.RC == TWRC.SUCCESS && pending.Count != 0);
@@ -173,10 +172,10 @@ namespace NTwain
HandleXferCode(sts); HandleXferCode(sts);
if (State >= STATE.S5) if (State >= STATE.S5)
{ {
_uiThreadMarshaller.Invoke(() => _uiThreadMarshaller.Send(obj =>
{ {
DisableSource(); ((TwainAppSession)obj!).DisableSource();
}); }, this);
} }
_inTransfer = false; _inTransfer = false;
} }
@@ -191,13 +190,11 @@ namespace NTwain
break; break;
case TWRC.CANCEL: case TWRC.CANCEL:
// might eventually have option to cancel this or all like transfer ready // might eventually have option to cancel this or all like transfer ready
if (TransferCanceled != null) _uiThreadMarshaller.Send(obj =>
{ {
_uiThreadMarshaller.Invoke(() => var twain = ((TwainAppSession)obj!);
{ twain.TransferCanceled?.Invoke(twain, new TransferCanceledEventArgs());
TransferCanceled.Invoke(this, new TransferCanceledEventArgs()); }, this);
});
};
TW_PENDINGXFERS pending = default; TW_PENDINGXFERS pending = default;
DGControl.PendingXfers.EndXfer(ref _appIdentity, ref _currentDS, ref pending); DGControl.PendingXfers.EndXfer(ref _appIdentity, ref _currentDS, ref pending);
// todo: also reset? // todo: also reset?

View File

@@ -23,7 +23,7 @@ namespace NTwain
/// <param name="exeFilePath"></param> /// <param name="exeFilePath"></param>
/// <param name="appLanguage"></param> /// <param name="appLanguage"></param>
/// <param name="appCountry"></param> /// <param name="appCountry"></param>
public TwainAppSession(IThreadMarshaller uiThreadMarshaller, public TwainAppSession(SynchronizationContext uiThreadMarshaller,
string exeFilePath, string exeFilePath,
TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA) : TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA) :
this(uiThreadMarshaller, FileVersionInfo.GetVersionInfo(exeFilePath), appLanguage, appCountry) this(uiThreadMarshaller, FileVersionInfo.GetVersionInfo(exeFilePath), appLanguage, appCountry)
@@ -35,7 +35,7 @@ namespace NTwain
/// <param name="appInfo"></param> /// <param name="appInfo"></param>
/// <param name="appLanguage"></param> /// <param name="appLanguage"></param>
/// <param name="appCountry"></param> /// <param name="appCountry"></param>
public TwainAppSession(IThreadMarshaller uiThreadMarshaller, public TwainAppSession(SynchronizationContext uiThreadMarshaller,
FileVersionInfo appInfo, FileVersionInfo appInfo,
TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA) : TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA) :
this(uiThreadMarshaller, this(uiThreadMarshaller,
@@ -57,7 +57,7 @@ namespace NTwain
/// <param name="appLanguage"></param> /// <param name="appLanguage"></param>
/// <param name="appCountry"></param> /// <param name="appCountry"></param>
/// <param name="supportedTypes"></param> /// <param name="supportedTypes"></param>
public TwainAppSession(IThreadMarshaller uiThreadMarshaller, public TwainAppSession(SynchronizationContext uiThreadMarshaller,
string companyName, string productFamily, string productName, string companyName, string productFamily, string productName,
Version productVersion, string productDescription = "", Version productVersion, string productDescription = "",
TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA, TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA,
@@ -104,7 +104,7 @@ namespace NTwain
#endif #endif
// test threads a bit // test threads a bit
//readonly BlockingCollection<MSG> _bgPendingMsgs = new(); //readonly BlockingCollection<MSG> _bgPendingMsgs = new();
private readonly IThreadMarshaller _uiThreadMarshaller; private readonly SynchronizationContext _uiThreadMarshaller;
bool _closeDsRequested; bool _closeDsRequested;
bool _inTransfer; bool _inTransfer;
readonly AutoResetEvent _xferReady = new(false); readonly AutoResetEvent _xferReady = new(false);