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.Reflection;
using System.Runtime.Versioning;
using System.Threading;
using System.Windows.Forms;
namespace WinFormSample
@@ -28,7 +29,7 @@ namespace WinFormSample
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.DefaultSourceChanged += Twain_DefaultSourceChanged;
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)
{
// 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;
case MSG.DEVICEEVENT:
if (DeviceEvent != null && DGControl.DeviceEvent.Get(ref _appIdentity, ref _currentDS, out TW_DEVICEEVENT de) == TWRC.SUCCESS)
{
_uiThreadMarshaller.BeginInvoke(() =>
_uiThreadMarshaller.Post(obj =>
{
try
{
DeviceEvent.Invoke(this, de);
var twain = (TwainAppSession)obj!;
twain.DeviceEvent!.Invoke(twain, de);
}
catch { }
});
}, this);
}
break;
}

View File

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

View File

@@ -60,16 +60,16 @@ namespace NTwain
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)
{
handled = CheckIfTwainMessage(hwnd, msg, wParam, lParam);
handled = WndProc(hwnd, msg, wParam, lParam);
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.
bool handled = false;

View File

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

View File

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