问题
Has anyone tried TwainDotNet for scanning with TWAIN API calls from .NET? Though it works well usually I've some issues with it when used along with WPF application using MVVM. Basically I'm calling Twain scanning functions from a Service, which in turn uses a 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();
}
ScanningComplete event handler is never fired when we are working with a BackgroundWorker. Any suggestions to resolve this issue?
回答1:
The fact that the Twain object requires a window handle in its object constructor suggests that something inside the Twain object requires message handling. Cross-thread message handling is tricky to begin with but even more so when it's happening inside an API.
If the twain API creates a window handle (overtly, such as a popup window or dialog, or secretly, such as for interprocess communication (IPC)) as part of one of the API functions you're calling from the background thread, that window handle will be bound to the thread it was created on - the background thread. All messages sent to that window handle will queue up waiting for the background thread to process them in a message loop. You don't have a message loop in your background thread, so that window handle will get stuck in limbo. It won't respond to window messages. Posted messages will go unanswered. SendMessage() will deadlock.
Even if this is not a window handle / message loop problem, it is very likely that if the Twain API was not explicitly and deliberately implemented with multithreading in mind, it will have problems when used across threads. You are creating the twain object in one thread and then using it in another thread, so this is a cross-thread situation. If you could create the twain object in the background thread and only use the twain object in the context of that background thread, this might work around thread affinity issues in the twain API implementation. When window handles and messages are involved, moving everything to the background thread is just as likely to make things worse.
The ability to use an object across threads does not come for free. If the twain API was not designed for use across threads, there is little you can do to make it work across threads. Your best bet is to keep the Twain object in the main UI thread.
回答2:
Have you tried removing the LINQ'ness from the code and put it into a separate function to actually test this out first, note that I have it wrapped up in a try/catch
block to see if there's any error, also notice that I created a simple class WorkerArgs
for passing the data around as it is non-LINQ code, it would be interesting to see what results there are (if any):
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
}
}
I couldn't help noticing, it's just after entering the code I noticed this:
Twain twain = new Twain(new WpfWindowMessageHook(_window))
Are you doing hooking or something like that within the background worker - perhaps there's a cross thread problem hence ScanningComplete
is not being fired? Just a thought, Can you clarify anyway?
来源:https://stackoverflow.com/questions/2257809/twaindotnet-scanning-using-twain-with-backgroundworker