如何在主线程上处理大型全局对象时不阻止工作线程中的主UI线程

时间:2014-07-31 16:37:11

标签: c# multithreading com thread-safety task

我正在使用外部磁带库进行卡片扫描,为了保持简单,我将其称为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线程阻塞的原因,我认为这可能是两件事之一:

  1. 我使用全局声明的scanLibraryReference变量来使用该库,甚至很难在一个任务中使用它,它可能在使用它时阻止主UI线程,因为它上面声明了变量。
  2. 所有ScanLib方法,根据文档可以抛出许多错误,这些错误都有数字(错误1001,1002,1003等),并且为了简化错误记录,文档要求我声明大约+100常量类似于:public const int SCANLIB_ERR_SCANFAILED = 1001;。所有这些"错误常数"在另一个MainForm的public partial class类型的文件中声明,所以也许从另一个任务(线程)使用这些常量可能会冻结主UI线程
  3. 这些是我的主要嫌疑人,但话说回来,你会认为我会解决它,但我还没有,这就是我的问题所在: 我将在整个程序的持续时间内不断需要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;
        }
    

    在此之后,一切都运转良好。我还没有完全理解它,但它有效,谢谢你们的帮助。

1 个答案:

答案 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;
    }
    //...
}