我正在使用外部磁带库进行卡片扫描,为了保持简单,我将其称为ScanLib。扫描过程很长,当然,它会在调用ScanLib.Scan()等方法时阻塞UI线程,因此我认为这是使用Tasks的好时机。这里是上下文的简化(或TLDR)代码(下面是完整代码以获取更多详细信息,但为了节省时间,这里总结一下):
public partial class MainForm : Form ()
{
//Here I initialize the reference variable which I will use to work with the Scanner library
ScanLib scanLibraryReference = new ScanLib();
// Form constructor
public Form()
{
//This initializes the scanner and it's library components, it runs until the program closes
Task scannerInitTask = Task.Run(() => scanLibraryReference.InitializeScanLibrary())
InitializeComponent();
}
private void Button_Click(object sender, EventArgs e) {
Task scannerTask = Task.Run(() => scanLibraryReference.ScanCard());
}
}
问题是,当我尝试扫描一张卡时,它冻结了主UI线程,甚至很难我在其他任务中同时运行ScanLib.InitializeScanLib()和ScanLib.ScanCard()方法来尝试不阻塞主线程UI线程,因为后者是耗时的。我已经阅读了主要UI线程阻塞的原因,我认为这可能是两件事之一:
这些是我的主要嫌疑人,但话说回来,你会认为我会解决它,但我还没有,这就是我的问题所在: 我将在整个程序的持续时间内不断需要ScanLib引用,但如果我在主UI线程上创建它,它将被阻止。我可以尝试创建一个新的任务,如: 任务backgroundWorker = Task.Run(()=> {ScanLib scanLibRef = new ScanLib(); scanLibRef.InitializeLibrary()}); 但是我的理解是这个变量现在将存在于这个主题上,并且无法从另一个变量中使用,或者可以吗?或者甚至如果我创建一个简单的线程来容纳变量,那么一旦完成声明变量,该线程就会死掉。我已经尝试过使用Thread函数执行此操作,但是当我按下按钮时,如何将其调回操作时会出现问题,并为其提供运行扫描程序的功能。任何人都可以建议一个解决方案,我如何声明一个我需要经常使用的全局变量而不阻塞主UI线程?
请求完整代码(对不起,如果它很长,所有Console.WriteLines都用于调试)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using ScannerTest;
using System.Threading;
namespace ScannerTest
{
public partial class Form1 : Form
{
// All 4 variables below are global and used extensively to call scanner methods
// Main Scan basic functions Library
NetScanW.SLibEx scanSlibxEx = new NetScanW.SLibEx();
// Main Scan extended functios Library
NetScanW.CImage scanCImage = new NetScanW.CImage();
// Main Scan OCR functions library
NetScanW.IdData scanIdData = new NetScanW.IdData();
// Main Scan extended functions 2 library
NetScanWex.SLibEx scanWexSlibxEx = new NetScanWex.SLibEx();
string ImageSource = @"C:\Scans\";
public Form1()
{
Task initTask = Task.Run(() => InitScanLibraries());
InitializeComponent();
Console.WriteLine("\nForm initialized succesfully.");
}
#region Button events
// The normal way of scanning which of course blocks the main UI thread
private void ScanCardNonAsync_Click(object sender, EventArgs e)
{
Console.WriteLine("");
Console.WriteLine("*********************");
Console.WriteLine("Starting new scan...");
string currentScan = ImageSource + "MyScan.bmp";
string modifiedScan = ImageSource + "MyEditedScan.bmp";
ScanCard(currentScan, modifiedScan);
OCRscan();
GetOCRData();
Console.WriteLine("Scan finalized..");
Console.WriteLine("*********************");
}
// My attempt at scanning asynchronously which still blocks the UI thread
private void ScanCardAsync_Click(object sender, EventArgs e)
{
Console.WriteLine("");
Console.WriteLine("*********************");
Console.WriteLine("Starting new scan...");
string currentScan = ImageSource + "MyScan.bmp";
string modifiedScan = ImageSource + "MyEditedScan.bmp";
// Here I chain the methods in a task chain to scan a Card
Task ScannerStart = new Task(() => ScanCard(currentScan, modifiedScan));
Task ScannerStep2 = ScannerStart.ContinueWith((x) => OCRscan());
Task ScannerStep3 = ScannerStep2.ContinueWith((y) => GetOCRData());
ScannerStart.Start();
}
#endregion
#region Methods
private void InitScanLibraries()
{
switch (scanSlibxEx.InitLibrary("49B2MFWE8WUJXLBW"))
{
case SLIB_ERR_SCANNER_BUSSY:
System.Console.WriteLine("ERROR: Scanner Busy...");
break;
case LICENSE_VALID:
System.Console.WriteLine("");
System.Console.WriteLine("**********************************");
System.Console.WriteLine("SlibxEx initialized succesfully...");
break;
case LICENSE_INVALID:
System.Console.WriteLine("ERROR: License Invalid");
break;
case LICENSE_EXPIRED:
System.Console.WriteLine("ERROR: License Expired");
break;
case SLIB_ERR_DRIVER_NOT_FOUND:
System.Console.WriteLine("ERROR: Driver not found");
break;
case SLIB_ERR_SCANNER_NOT_FOUND:
System.Console.WriteLine("ERROR: Scanner not found");
break;
}
switch (scanIdData.InitLibrary("49B2MFWE8WUJXLBW"))
{
case SLIB_ERR_SCANNER_BUSSY:
System.Console.WriteLine("ERROR: Scanner Busy...");
break;
case LICENSE_VALID:
System.Console.WriteLine("License validation succesful...");
break;
case LICENSE_INVALID:
System.Console.WriteLine("ERROR: License Invalid");
break;
case LICENSE_EXPIRED:
System.Console.WriteLine("ERROR: License Expired");
break;
case SLIB_ERR_DRIVER_NOT_FOUND:
System.Console.WriteLine("ERROR: Driver not found");
break;
case SLIB_ERR_SCANNER_NOT_FOUND:
System.Console.WriteLine("ERROR: Scanner not found");
break;
case GENERAL_ERR_PLUG_NOT_FOUND:
System.Console.WriteLine("ERROR: Attatched scanner is not one of the following:\n ScanShell 600 \n ScanShell 800 \n ScanShell1000");
break;
case SLIB_LIBRARY_ALREADY_INITIALIZED:
System.Console.WriteLine("ERROR: Call ignored, library already initialized");
break;
}
}
private void ScanCard(string ImagePath, string ModifiedImagePath)
{
Console.WriteLine("Attempting scan...");
switch (scanSlibxEx.ScanToFile(ImagePath))
{
case SLIB_ERR_NONE:
Console.WriteLine("Scan succesful...");
break;
case SLIB_ERR_SCANNER_BUSSY:
Console.WriteLine("ERROR: Scanner is busy...");
break;
case LICENSE_INVALID:
Console.WriteLine("ERROR: License invalid");
break;
case SLIB_ERR_SCANNER_NOT_FOUND:
Console.WriteLine("ERROR: Scanner not found");
break;
case SLIB_ERR_SCANNER_GENERAL_FAIL:
Console.WriteLine("ERROR: Scanner general fail");
break;
case SLIB_ERR_HARDWARE_ERROR:
Console.WriteLine("ERROR: Hardware error");
break;
case SLIB_ERR_PAPER_FED_ERROR:
Console.WriteLine("ERROR: Paper fed error");
break;
case SLIB_ERR_SCANABORT:
Console.WriteLine("ERROR: Scan aborted");
break;
case SLIB_ERR_NO_PAPER:
Console.WriteLine("ERROR: No paper");
break;
case SLIB_ERR_PAPER_JAM:
Console.WriteLine("ERROR: Paper jammed");
break;
case SLIB_ERR_FILE_IO_ERROR:
Console.WriteLine("ERROR: File I/O error");
break;
case SLIB_ERR_PRINTER_PORT_USED:
Console.WriteLine("ERROR: Printer port used");
break;
case SLIB_ERR_OUT_OF_MEMORY:
Console.WriteLine("ERROR: Out of memory");
break;
}
//scanCImage.RotateImage(ImageSource, 90, 1, ModifiedImagePath);
}
private void OCRscan()
{
Console.WriteLine("Attempting OCR extraction...");
string data = "";
int region = scanIdData.AutoDetectState(data);
// Check for card region
switch (region)
{
case ID_ERR_USA_TEMPLATES_NOT_FOUND:
Console.WriteLine("ERROR: No USA templates found");
break;
case INVALID_INTERNAL_IMAGE:
Console.WriteLine("ERROR: No internal image loaded");
break;
case ID_ERR_STATE_NOT_SUPORTED:
Console.WriteLine("ERROR: State not supported");
break;
case ID_ERR_STATE_NOT_RECOGNIZED:
Console.WriteLine("ERROR: State not recognized");
break;
default:
Console.WriteLine("Region catch succesful");
break;
}
// Begin OCR extraction
string data2 = "";
Console.WriteLine("Attempting data extraction...");
switch (scanIdData.ProcState(data2, region))
{
case ID_TRUE:
Console.WriteLine("Data extraction succesful.");
break;
case LICENSE_INVALID:
Console.WriteLine("ERROR: LICENSE_INVALID");
break;
case SLIB_ERR_SCANNER_NOT_FOUND:
Console.WriteLine("ERROR: SLIB_ERR_SCANNER_NOT_FOUND. ");
break;
case SLIB_ERR_INVALID_SCANNER:
Console.WriteLine("ERROR: SLIB_ERR_INVALID_SCANNER. ");
break;
case ID_ERR_STATE_NOT_SUPORTED:
Console.WriteLine("ERROR: ID_ERR_STATE_NOT_SUPORTED. ");
break;
case INVALID_INTERNAL_IMAGE:
Console.WriteLine("ERROR: INVALID_INTERNAL_IMAGE. ");
break;
default:
Console.WriteLine("ERROR: Uncatched exception in Form1.OCRScan()");
break;
}
// Data copying to local
Console.WriteLine("Copying data locally...");
if (scanIdData.RefreshData() != 0) Console.WriteLine("Data copied succesfully."); else Console.WriteLine("ERROR: Problem while copying data");
}
private void GetOCRData()
{
//loc* Variables are locally declared global variables, while the scanIdData.* are library variables where OCR scan results are saved
Console.WriteLine("Saving data locally...");
locName = scanIdData.Name;
locNameFirst = scanIdData.NameFirst;
locNameMiddle = scanIdData.NameMiddle;
locNameLast = scanIdData.NameLast;
locNameSuffix = scanIdData.NameSuffix;
locID = scanIdData.Id;
locLicense = scanIdData.license;
locIssueDate = scanIdData.IssueDate;
locAddress = scanIdData.Address;
locExperationDate = scanIdData.ExpirationDate;
locCSC = scanIdData.CSC;
locCity = scanIdData.City;
locEyes = scanIdData.Eyes;
locDup_Test = scanIdData.Dup_Test;
locState = scanIdData.State;
locHair = scanIdData.Hair;
locEndorsements = scanIdData.Endorsements;
locZip = scanIdData.Zip;
locHeight = scanIdData.Height;
locFee = scanIdData.Fee;
locCounty = scanIdData.County;
locClass = scanIdData.Class;
locRestriction = scanIdData.Restriction;
locDateOfBirth = scanIdData.DateOfBirth;
locSex = scanIdData.Sex;
locSigNum = scanIdData.SigNum;
locType = scanIdData.Type;
locWeight = scanIdData.Weight;
locAddress2 = scanIdData.Address2;
locAddress3 = scanIdData.Address3;
locAddress4 = scanIdData.Address4;
locAddress5 = scanIdData.Address5;
locText1 = scanIdData.Text1;
locText2 = scanIdData.Text2;
locText3 = scanIdData.Text3;
Console.WriteLine("Data saved succesfully.");
}
#endregion
}
}
修改
我做了Onur的建议,是的,阻止主UI线程的是全局变量scan Lib 。我运行了以下代码,它没有冻结主UI线程:
Task debugTask = Task.Run(() =>
{
// All 4 variables below are global and used extensively to call scanner methods
// Main Scan basic functions Library
NetScanW.SLibEx scanSlibxEx = new NetScanW.SLibEx();
// Main Scan extended functios Library
NetScanW.CImage scanCImage = new NetScanW.CImage();
// Main Scan OCR functions library
NetScanW.IdData scanIdData = new NetScanW.IdData();
// Main Scan extended functions 2 library
NetScanWex.SLibEx scanWexSlibxEx = new NetScanWex.SLibEx();
string ImageSource = @"C:\Scans\";
string currentScan = ImageSource + "MyScan.bmp";
string modifiedScan = ImageSource + "MyEditedScan.bmp";
InitScanLibraries(scanSlibxEx, scanIdData);
ScanCard(currentScan, modifiedScan, scanSlibxEx);
});
是的,它非常非常混乱,但它起作用并没有冻结。我所做的只是声明全局变量,初始化库并在同一个线程中运行扫描,当然,它没有冻结主UI线程,但它远非我想要的。我需要库保持初始化,在辅助线程中运行,当我需要扫描某些内容时,让它从ScanLib引用变量调用ScanLib方法,因为我不知道在哪里,我仍然难倒这样它就不会阻止主UI线程。我将从Onur尝试下面的答案,看看会发生什么。
最终编辑
为了最终确定我的问题,我想添加已解决的代码以防其他人需要它。根据Orun的回答,我不是像ScanLib refScanLib = new ScanLib()那样将顶部的全局变量声明为顶部,而是将它们声明为null对象,如下所示:ScanLib refScanLib = null,在Form构造函数中,我添加了一个名为InitializeVariables()的新方法,该方法执行以下操作:
public void InitializeVariables()
{
NetScanW.SLibEx scanSLibExx = null;
NetScanW.IdData scanIdDataa = null;
NetScanW.CImage scanCImagee = null;
NetScanWex.SLibEx scanWexSLibExx = null;
var th = new Thread(() =>
{
scanSLibExx = new NetScanW.SLibEx();
scanIdDataa = new NetScanW.IdData();
scanCImagee = new NetScanW.CImage();
scanWexSLibExx = new NetScanWex.SLibEx();
});
th.SetApartmentState(ApartmentState.MTA);
th.Start();
th.Join();
this.scanSlibxEx = scanSLibExx;
this.scanIdData = scanIdDataa;
this.scanCImage = scanCImagee;
this.scanWexSlibxEx = scanWexSLibExx;
}
在此之后,一切都运转良好。我还没有完全理解它,但它有效,谢谢你们的帮助。
答案 0 :(得分:1)
在使用COM库的情况下,这对我来说很有用。
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread] // <= I needed to add this attribute
private static void Main()
{
//...
}
}
public partial class MainForm : Form ()
{
// you can call this in the InitializeComponents() for instance
void someMethodInYourFormIERunningOnTheUIThread()
{
ScanLib scanLib = null;
var th = new Thread(() =>
{
scanLib = new ScanLib();
});
th.SetApartmentState(ApartmentState.MTA); // <== this prevented binding the UI thread for some operations
th.Start();
th.Join();
this.scanLibraryReference = scanLib;
}
//...
}