Experimenting with bg thread processing.

This commit is contained in:
Eugene Wang 2023-04-02 22:36:58 -04:00
parent 84251a6a5e
commit 679a00818a
8 changed files with 220 additions and 20 deletions

View File

@ -14,7 +14,7 @@ namespace SampleConsole
{
TwainPlatform.PreferLegacyDSM = true;
var twain = new TwainSession(Assembly.GetExecutingAssembly().Location);
var twain = new TwainSession(new InPlaceMarshaller(), Assembly.GetExecutingAssembly().Location);
twain.StateChanged += Twain_StateChanged;
var hwnd = IntPtr.Zero; // required for windows

View File

@ -18,10 +18,17 @@ namespace WinForm32
Text += TwainPlatform.Is32bit ? " 32bit" : " 64bit";
TwainPlatform.PreferLegacyDSM = false;
twain = new TwainSession(Assembly.GetExecutingAssembly().Location);
twain = new TwainSession(new WinformMarshaller(this), Assembly.GetExecutingAssembly().Location);
twain.StateChanged += Twain_StateChanged;
twain.DefaultSourceChanged += Twain_DefaultSourceChanged;
twain.CurrentSourceChanged += Twain_CurrentSourceChanged;
this.Disposed += Form1_Disposed;
}
private void Form1_Disposed(object? sender, EventArgs e)
{
twain.Dispose();
}
private void Twain_CurrentSourceChanged(TwainSession arg1, TW_IDENTITY_LEGACY ds)

View File

@ -0,0 +1,49 @@
#if WINDOWS || NETFRAMEWORK
using System;
using System.Windows.Forms;
using System.Windows.Threading;
namespace NTwain
{
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);
}
}
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

@ -0,0 +1,46 @@
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.
/// </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

@ -90,19 +90,13 @@ namespace NTwain
switch (msg)
{
case MSG.XFERREADY:
break;
case MSG.DEVICEEVENT:
// use diff thread to get it
//if (DGControl.DeviceEvent.Get(ref _appIdentity, ref _currentDS, out TW_DEVICEEVENT de) == STS.SUCCESS)
//{
//}
break;
case MSG.CLOSEDSOK:
DisableSource();
break;
case MSG.CLOSEDSREQ:
DisableSource();
// the reason we post these to the background is
// if they're coming from UI message loop then
// this needs to return asap
_bgPendingMsgs.Add(msg);
break;
}
}

View File

@ -130,5 +130,10 @@ namespace NTwain
/// Fires when <see cref="CurrentSource"/> changes.
/// </summary>
public event Action<TwainSession, TW_IDENTITY_LEGACY>? CurrentSourceChanged;
/// <summary>
/// Fires when the source has some device event happening.
/// </summary>
public event Action<TwainSession, TW_DEVICEEVENT>? DeviceEvent;
}
}

View File

@ -0,0 +1,14 @@
using NTwain.Data;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;
namespace NTwain
{
// this file contains various xfer methods
partial class TwainSession
{
}
}

View File

@ -1,37 +1,43 @@
using NTwain.Data;
using NTwain.Triplets;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Text;
using System.Threading;
namespace NTwain
{
// this file contains initialization/cleanup things.
public partial class TwainSession
public partial class TwainSession : IDisposable
{
static bool __encodingRegistered;
/// <summary>
/// Creates TWAIN session with app info derived an executable file.
/// </summary>
/// <param name="uiThreadMarshaller"></param>
/// <param name="exeFilePath"></param>
/// <param name="appLanguage"></param>
/// <param name="appCountry"></param>
public TwainSession(string exeFilePath,
TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA) :
this(FileVersionInfo.GetVersionInfo(exeFilePath),
appLanguage, appCountry)
public TwainSession(IThreadMarshaller uiThreadMarshaller,
string exeFilePath,
TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA) :
this(uiThreadMarshaller, FileVersionInfo.GetVersionInfo(exeFilePath), appLanguage, appCountry)
{ }
/// <summary>
/// Creates TWAIN session with app info derived from a <see cref="FileVersionInfo"/> object.
/// </summary>
/// <param name="uiThreadMarshaller"></param>
/// <param name="appInfo"></param>
/// <param name="appLanguage"></param>
/// <param name="appCountry"></param>
public TwainSession(FileVersionInfo appInfo,
public TwainSession(IThreadMarshaller uiThreadMarshaller,
FileVersionInfo appInfo,
TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA) :
this(appInfo.CompanyName ?? "",
this(uiThreadMarshaller,
appInfo.CompanyName ?? "",
appInfo.ProductName ?? "",
appInfo.ProductName ?? "",
new Version(appInfo.FileVersion ?? "1.0"),
@ -40,6 +46,7 @@ namespace NTwain
/// <summary>
/// Creates TWAIN session with explicit app info.
/// </summary>
/// <param name="uiThreadMarshaller"></param>
/// <param name="companyName"></param>
/// <param name="productFamily"></param>
/// <param name="productName"></param>
@ -48,11 +55,13 @@ namespace NTwain
/// <param name="appLanguage"></param>
/// <param name="appCountry"></param>
/// <param name="supportedTypes"></param>
public TwainSession(string companyName, string productFamily, string productName,
public TwainSession(IThreadMarshaller uiThreadMarshaller,
string companyName, string productFamily, string productName,
Version productVersion, string productDescription = "",
TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA,
DG supportedTypes = DG.IMAGE)
{
// todo: find a better place for this
if (!__encodingRegistered)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
@ -79,10 +88,86 @@ namespace NTwain
_legacyCallbackDelegate = LegacyCallbackHandler;
_osxCallbackDelegate = OSXCallbackHandler;
_uiThreadMarshaller = uiThreadMarshaller;
StartBackgroundThread();
}
internal IntPtr _hwnd;
internal TW_USERINTERFACE _userInterface;
// test this a bit
readonly BlockingCollection<MSG> _bgPendingMsgs = new();
private readonly IThreadMarshaller _uiThreadMarshaller;
private bool disposedValue;
void StartBackgroundThread()
{
Thread t = new(BackgroundThreadLoop)
{
IsBackground = true
};
t.SetApartmentState(ApartmentState.STA); // just in case
t.Start();
}
private void BackgroundThreadLoop(object? obj)
{
foreach (var msg in _bgPendingMsgs.GetConsumingEnumerable())
{
switch (msg)
{
case MSG.CLOSEDS:
case MSG.CLOSEDSREQ:
// this should be done on ui thread (or same one that enabled the ds)
_uiThreadMarshaller.BeginInvoke(() =>
{
DisableSource();
});
break;
case MSG.DEVICEEVENT:
if (DGControl.DeviceEvent.Get(ref _appIdentity, ref _currentDS, out TW_DEVICEEVENT de) == STS.SUCCESS)
{
_uiThreadMarshaller.BeginInvoke(() =>
{
DeviceEvent?.Invoke(this, de);
});
}
break;
case MSG.XFERREADY:
break;
}
}
Debug.WriteLine("Ending BackgroundThreadLoop...");
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// this will end the bg thread
_bgPendingMsgs.CompleteAdding();
}
disposedValue = true;
}
}
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~TwainSession()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Loads and opens the TWAIN data source manager.