From 4e8f52f696fdc9d910911912d5144c03a4d4a454 Mon Sep 17 00:00:00 2001
From: Eugene Wang <8755753+soukoku@users.noreply.github.com>
Date: Tue, 3 Feb 2026 19:35:00 -0500
Subject: [PATCH] Experiment with ownership idea for transferred event so
caller won't need to dispose it.
---
samples/ConsoleSample/Program.cs | 1 -
samples/KodakTester/MainForm.cs | 1 -
samples/WinForm32/Form1.cs | 16 ++++---
samples/WinUI3/MainWindow.xaml.cs | 4 --
src/NTwain/Events/TransferredEventArgs.cs | 56 +++++++++++++++++++++--
src/NTwain/TransferLoopThread.Logic.cs | 10 +---
src/NTwain/TwainAppSession.TwainState.cs | 13 ++++--
7 files changed, 72 insertions(+), 29 deletions(-)
diff --git a/samples/ConsoleSample/Program.cs b/samples/ConsoleSample/Program.cs
index ba5e6b1..b555ab5 100644
--- a/samples/ConsoleSample/Program.cs
+++ b/samples/ConsoleSample/Program.cs
@@ -141,6 +141,5 @@ internal class Program
{
Console.WriteLine($"BUMMER! No twain data #{++xferCount} on thread {Environment.CurrentManagedThreadId}.");
}
- e.Dispose();
}
}
diff --git a/samples/KodakTester/MainForm.cs b/samples/KodakTester/MainForm.cs
index 0df0413..45b85a3 100644
--- a/samples/KodakTester/MainForm.cs
+++ b/samples/KodakTester/MainForm.cs
@@ -98,7 +98,6 @@ public partial class MainForm : Form
var path = info.FileName.ToString();
LogIt($"Received {e.ImageInfo.PixelType} {info.Format} in {e.ImageInfo.Compression} compressed file {path}");
}
- e.Dispose();
LogIt("");
}
diff --git a/samples/WinForm32/Form1.cs b/samples/WinForm32/Form1.cs
index 80773f8..3bbc683 100644
--- a/samples/WinForm32/Form1.cs
+++ b/samples/WinForm32/Form1.cs
@@ -131,20 +131,22 @@ public partial class Form1 : Form
if (_useThreadForImag)
{
// bad thread example but whatev. should use a dedicated thread of some sort for real
+ var data = e.TakeDataOwnership();
Task.Run(() =>
{
- HandleTransferredData(e);
+ HandleTransferredData(data);
+ data?.Dispose();
});
}
else
{
- HandleTransferredData(e);
+ HandleTransferredData(e.Data);
}
}
- private void HandleTransferredData(TransferredEventArgs e)
+ private void HandleTransferredData(BufferedData? data)
{
- if (e.Data != null)
+ if (data != null)
{
try
{
@@ -153,7 +155,7 @@ public partial class Form1 : Form
if (_useSystemDrawing)
{
- using (var img = Image.FromStream(e.Data.AsStream()))
+ using (var img = Image.FromStream(data.AsStream()))
{
if (img.PixelFormat == System.Drawing.Imaging.PixelFormat.Format1bppIndexed ||
img.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed)
@@ -174,7 +176,7 @@ public partial class Form1 : Form
}
else
{
- using (var img = new ImageMagick.MagickImage(e.Data.AsSpan()))
+ using (var img = new ImageMagick.MagickImage(data.AsSpan()))
{
var format = ImageMagick.MagickFormat.Png;
if (img.ColorType == ImageMagick.ColorType.Palette)
@@ -198,7 +200,7 @@ public partial class Form1 : Form
catch { }
finally
{
- e.Dispose();
+ data.Dispose();
}
}
}
diff --git a/samples/WinUI3/MainWindow.xaml.cs b/samples/WinUI3/MainWindow.xaml.cs
index d3473d6..52fc586 100644
--- a/samples/WinUI3/MainWindow.xaml.cs
+++ b/samples/WinUI3/MainWindow.xaml.cs
@@ -110,10 +110,6 @@ public sealed partial class MainWindow : Window
}
}
catch { }
- finally
- {
- e.Dispose();
- }
}
private void Session_SourceDisabled(TwainAppSession sender, TWIdentityWrapper e)
diff --git a/src/NTwain/Events/TransferredEventArgs.cs b/src/NTwain/Events/TransferredEventArgs.cs
index fe9f607..e8cf4b6 100644
--- a/src/NTwain/Events/TransferredEventArgs.cs
+++ b/src/NTwain/Events/TransferredEventArgs.cs
@@ -34,11 +34,14 @@ public class TransferredEventArgs : EventArgs, IDisposable
///
public bool IsImage { get; }
- private readonly BufferedData? _data;
+ private BufferedData? _data;
+ private bool _dataOwnershipTransferred;
+
///
- /// The complete file data if memory was involved in the transfer.
- /// IMPORTANT: Content of this array will not be valid once
- /// this event arg has been disposed.
+ /// Gets the transferred data.
+ /// IMPORTANT: This data is only valid during the event handler execution.
+ /// If you need to keep the data after the event handler returns, call first.
+ /// Otherwise, the data will be automatically disposed when the event completes.
///
public BufferedData? Data => _data;
@@ -73,8 +76,51 @@ public class TransferredEventArgs : EventArgs, IDisposable
return _twain.WrapInSTS(DGImage.ExtImageInfo.Get(_twain.AppIdentity, _twain.CurrentSource, ref container));
}
+ ///
+ /// Transfers ownership of the in-memory data to the caller, preventing automatic disposal.
+ /// Use this when you need to process the data asynchronously or keep it beyond the event handler scope.
+ /// After calling this, you MUST manually dispose the returned BufferedData when finished.
+ ///
+ ///
+ /// The buffered data with transferred ownership, or null if no data exists or ownership was already transferred.
+ ///
+ ///
+ ///
+ /// private async void OnTransferred(TwainAppSession sender, TransferredEventArgs e)
+ /// {
+ /// var data = e.TakeDataOwnership(); // Take ownership
+ /// if (data != null)
+ /// {
+ /// try
+ /// {
+ /// await ProcessDataAsync(data); // Can use beyond event handler
+ /// }
+ /// finally
+ /// {
+ /// data.Dispose(); // Must dispose when done
+ /// }
+ /// }
+ /// }
+ ///
+ ///
+ public BufferedData? TakeDataOwnership()
+ {
+ if (_dataOwnershipTransferred || _data == null)
+ return null;
+
+ _dataOwnershipTransferred = true;
+ var data = _data;
+ _data = null;
+ return data;
+ }
+
public void Dispose()
{
- _data?.Dispose();
+ // Only dispose if ownership wasn't transferred
+ if (!_dataOwnershipTransferred && _data != null)
+ {
+ _data.Dispose();
+ _data = null;
+ }
}
}
\ No newline at end of file
diff --git a/src/NTwain/TransferLoopThread.Logic.cs b/src/NTwain/TransferLoopThread.Logic.cs
index 5370f08..57d0a88 100644
--- a/src/NTwain/TransferLoopThread.Logic.cs
+++ b/src/NTwain/TransferLoopThread.Logic.cs
@@ -224,10 +224,7 @@ partial class TransferLoopThread
var args = new TransferredEventArgs(info, data);
_twain.RaiseTransferred(args);
}
- catch
- {
- data.Dispose();
- }
+ catch { }
}
}
@@ -314,10 +311,7 @@ partial class TransferLoopThread
var args = new TransferredEventArgs(_twain, info, null, data);
_twain.RaiseTransferred(args);
}
- catch
- {
- data.Dispose();
- }
+ catch { }
}
diff --git a/src/NTwain/TwainAppSession.TwainState.cs b/src/NTwain/TwainAppSession.TwainState.cs
index 5841ca2..6504090 100644
--- a/src/NTwain/TwainAppSession.TwainState.cs
+++ b/src/NTwain/TwainAppSession.TwainState.cs
@@ -66,7 +66,7 @@ partial class TwainAppSession
internal void RaiseTransferred(TransferredEventArgs args)
{
// no wait here, app can handle at its leisure
- RaiseEvent(Transferred, args);
+ RaiseEvent(Transferred, args, static e => e.Dispose());
}
@@ -139,17 +139,24 @@ partial class TwainAppSession
/// The type of event arguments.
/// The event handler to invoke.
/// The event arguments.
- protected void RaiseEvent(EventHandler? eventHandler, TEventArgs eventArgs)
+ ///
+ protected void RaiseEvent(EventHandler? eventHandler, TEventArgs eventArgs,
+ Action? completeCallback = null)
{
if (eventHandler == null) return;
if (AppThreadContext != null)
{
- AppThreadContext.Post(_ => eventHandler(this, eventArgs), null);
+ AppThreadContext.Post(_ =>
+ {
+ eventHandler(this, eventArgs);
+ completeCallback?.Invoke(eventArgs);
+ }, null);
}
else
{
eventHandler(this, eventArgs);
+ completeCallback?.Invoke(eventArgs);
}
}