Experiment with ownership idea for transferred event so caller won't need to dispose it.

This commit is contained in:
Eugene Wang
2026-02-03 19:35:00 -05:00
parent 5c288987ca
commit 4e8f52f696
7 changed files with 72 additions and 29 deletions
-1
View File
@@ -141,6 +141,5 @@ internal class Program
{
Console.WriteLine($"BUMMER! No twain data #{++xferCount} on thread {Environment.CurrentManagedThreadId}.");
}
e.Dispose();
}
}
-1
View File
@@ -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("");
}
+9 -7
View File
@@ -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();
}
}
}
-4
View File
@@ -110,10 +110,6 @@ public sealed partial class MainWindow : Window
}
}
catch { }
finally
{
e.Dispose();
}
}
private void Session_SourceDisabled(TwainAppSession sender, TWIdentityWrapper e)
+51 -5
View File
@@ -34,11 +34,14 @@ public class TransferredEventArgs : EventArgs, IDisposable
/// </summary>
public bool IsImage { get; }
private readonly BufferedData? _data;
private BufferedData? _data;
private bool _dataOwnershipTransferred;
/// <summary>
/// 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 <see cref="TakeDataOwnership"/> first.
/// Otherwise, the data will be automatically disposed when the event completes.
/// </summary>
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));
}
/// <summary>
/// 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.
/// </summary>
/// <returns>
/// The buffered data with transferred ownership, or null if no data exists or ownership was already transferred.
/// </returns>
/// <example>
/// <code>
/// 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
/// }
/// }
/// }
/// </code>
/// </example>
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;
}
}
}
+2 -8
View File
@@ -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 { }
}
+10 -3
View File
@@ -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
/// <typeparam name="TEventArgs">The type of event arguments.</typeparam>
/// <param name="eventHandler">The event handler to invoke.</param>
/// <param name="eventArgs">The event arguments.</param>
protected void RaiseEvent<TEventArgs>(EventHandler<TwainAppSession, TEventArgs>? eventHandler, TEventArgs eventArgs)
/// <param name="completeCallback"></param>
protected void RaiseEvent<TEventArgs>(EventHandler<TwainAppSession, TEventArgs>? eventHandler, TEventArgs eventArgs,
Action<TEventArgs>? 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);
}
}