使用 TWAIN 和 BackgroundWorker 进行 TwainDotNet 扫描
Posted
技术标签:
【中文标题】使用 TWAIN 和 BackgroundWorker 进行 TwainDotNet 扫描【英文标题】:TwainDotNet Scanning using TWAIN with BackgroundWorker 【发布时间】:2011-01-16 12:00:34 【问题描述】:有没有人尝试使用TwainDotNet 使用来自 .NET 的 TWAIN API 调用进行扫描?虽然它通常运行良好,但在与使用 MVVM 的 WPF 应用程序一起使用时,我会遇到一些问题。基本上我是从一个 Service 调用 Twain 扫描函数,而 Service 又使用一个 BackgroundWorker。
List<BitmapSource> bitmapSources = new List<BitmapSource>();
Twain twain = new Twain(new WpfWindowMessageHook(_window));
ScanSettings settings = new ScanSettings() ShowTwainUI = false ;
using (BackgroundWorker worker = new BackgroundWorker())
worker.DoWork += (sndr, evnt) =>
AutoResetEvent waitHandle = new AutoResetEvent(false);
EventHandler scanCompleteHandler = (se, ev) => waitHandle.Set(); ;
twain.ScanningComplete += scanCompleteHandler;
twain.StartScanning(settings);
waitHandle.WaitOne();
if (twain.Images.Count > 0)
foreach (var image in twain.Images)
BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(new Bitmap(image).GetHbitmap(),
IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
bitmapSources.Add(bitmapSource);
;
worker.RunWorkerCompleted += (sndr, evnt) => image1.Source = bitmapSources[0]; ;
worker.RunWorkerAsync();
当我们使用 BackgroundWorker 时,永远不会触发 ScanningComplete 事件处理程序。有解决此问题的建议吗?
【问题讨论】:
Raj,我从 google TwainDotNet 问题页面下载了您的示例项目。我想对 backgroundworker 做同样的事情,因为我想在扫描时在窗口上显示进度/状态。我在访问图像时遇到了同样的问题。但是,在后台工作人员中扫描时,我也无法让窗口做出响应。当您使用此解决方案进行扫描时,您是否能够让窗口更新/响应?请告诉我。 戴夫,示例解决方案应该这样做对吗? 不。我想在窗口上放一个进度条并显示一些反馈。 DoWork 正在处理扫描。所以,我需要另一个线程来报告进度。我启动了一个 DispatcherTimer 并调用: Dispatcher.Invoke(updatePbDelegate, System.Windows.Threading.DispatcherPriority.Background, new object[] ProgressBar.ValueProperty, progressBarValue );但是在扫描完成之前,更新进度条的事件不会触发。如果您有其他想法,请告诉我。 【参考方案1】:吐温对象在其对象构造函数中需要一个窗口句柄这一事实表明吐温对象内部的某些东西需要消息处理。跨线程消息处理一开始很棘手,但当它发生在 API 内部时更是如此。
如果 twain API 创建一个窗口句柄(公开地,例如弹出窗口或对话框,或秘密地,例如用于进程间通信 (IPC))作为您从后台线程调用的 API 函数之一的一部分,该窗口句柄将绑定到创建它的线程 - 后台线程。发送到该窗口句柄的所有消息都将排队等待后台线程在消息循环中处理它们。您的后台线程中没有消息循环,因此窗口句柄将陷入困境。它不会响应窗口消息。发布的消息将得不到答复。 SendMessage() 会死锁。
即使这不是窗口句柄/消息循环问题,如果 Twain API 没有显式和刻意地实现多线程,它很可能会在跨线程使用时出现问题。您正在一个线程中创建 twain 对象,然后在另一个线程中使用它,因此这是一种跨线程情况。如果您可以在后台线程中创建 twain 对象并仅在该后台线程的上下文中使用 twain 对象,那么这可能会解决 twain API 实现中的线程关联性问题。当涉及到窗口句柄和消息时,将所有内容移至后台线程同样可能使事情变得更糟。
跨线程使用对象的能力不是免费的。如果 twain API 不是为跨线程使用而设计的,那么您几乎无法使其跨线程工作。最好的办法是将 Twain 对象保留在主 UI 线程中。
【讨论】:
[@dthorpe] 你的猜测都是正确的。 TWAIN 在线程中工作正常,但最好将所有 TWAIN 调用限制在该线程中,并且该线程必须具有消息泵,即是“UI 线程”。【参考方案2】:您是否尝试过从代码中删除 LINQ'ness 并将其放入单独的函数中以首先对其进行实际测试,请注意我已将其包裹在 try/catch
块中以查看是否有任何错误,还要注意我创建了一个简单的类 WorkerArgs
用于传递数据,因为它是非 LINQ 代码,看看有什么结果(如果有的话)会很有趣:
public class WorkerArgs
public List<BitMapSource> _bitmapSources;
public Twain _twain;
public ScanSettings _settings;
List<BitmapSource> bitmapSources = new List<BitmapSource>();
Twain twain = new Twain(new WpfWindowMessageHook(_window));
ScanSettings settings = new ScanSettings() ShowTwainUI = false ;
WorkerArgs wArgs = new WorkerArgs();
wArgs._bitmapSources = bitmapSources;
wArgs._twain = twain;
wArgs._settings = settings;
using (BackgroundWorker worker = new BackgroundWorker())
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync((WorkerArgs)wArgs);
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
try
image1.Source = (WorkerArgs(e.Argument))._bitmapSources[0];
catch(Exception up)
throw up; // :P
void worker_DoWork(object sender, DoWorkEventArgs e)
try
WorkerArgs thisArgs = (WorkerArgs)e.Argument as WorkerArgs;
if (thisArgs != null)
AutoResetEvent waitHandle = new AutoResetEvent(false);
EventHandler scanCompleteHandler = (se, ev) => waitHandle.Set(); ;
thisArgs._twain.ScanningComplete += scanCompleteHandler;
thisArgs._twain.StartScanning(settings);
waitHandle.WaitOne();
if (thisArgs._twain.Images.Count > 0)
foreach (var image in twain.Images)
BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(new Bitmap(image).GetHbitmap(),
IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
thisArgs._bitmapSources.Add(bitmapSource);
catch(Exception up)
throw up; // :P
我不禁注意到,输入代码后我才注意到:
Twain twain = new Twain(new WpfWindowMessageHook(_window))
您是否在后台工作人员中进行挂钩或类似操作 - 可能存在跨线程问题,因此 ScanningComplete
没有被解雇?只是一个想法,你能澄清一下吗?
【讨论】:
WpfWindowMessageHook 需要一个句柄才能工作,所以我传递了该值,但为了避免跨线程问题,我将 WpfWindowMessageHook 中的 IntPtr WindowHandle 更改为返回 (IntPtr)_window.Dispatcher.Invoke(new Func(() => _interopHelper.Handle));这已经解决了您正确预期的跨线程问题。这篇文章是在解决该问题后发布的。现在让我试试你的解决方案。 嗨,Tommie,您的代码更改也不起作用。我已经删除了 BackgroundWorker 以暂时设置它。但是在处理高 DPI (600+) 和启用文档进纸器的扫描时,TwainDotNet 库出现内存不足异常。使用 ADF 扫描时通常超过 3 页会引发内存异常。以上是关于使用 TWAIN 和 BackgroundWorker 进行 TwainDotNet 扫描的主要内容,如果未能解决你的问题,请参考以下文章
利用backgroundwork----递归读取网页源代码,并下载href链接中的文件