C#打印和线程

Nat*_*han 7 c# printing multithreading

我需要用户能够扫描一系列项目,并且每个项目都打印出x个标签.我目前正在尝试使用后台工作程序来完成此操作,但我遇到了一个问题,他们正在如此快速地扫描项目,并且有很多标签要为后台工作人员窒息的每个项目打印.这就是我为每次扫描生成后台工作线程的方法,因为在打印大量标签时会发生争用.

 private void RunPrintWorker()
    {
        if (printWorker.IsBusy)
        {
            printWorker = new BackgroundWorker();
            printWorker.DoWork += new DoWorkEventHandler(printWorker_DoWork);
            printWorker.RunWorkerAsync();
        }
        else
            printWorker.RunWorkerAsync();
    }
Run Code Online (Sandbox Code Playgroud)

我没有得到后台工作者的任何例外,它似乎没有足够快地创建线程.我是使用多线程的新手,所以有人能指出我做错的方向吗?

谢谢.

编辑:感谢大家的建议和阅读材料,这应该真的有帮助.打印标签的顺序并不重要,因为它们扫描速度非常快,标签也只打印到一台打印机上.在我实现启动并运行后,我将标记答案.

编辑:奥斯汀,下面是我如何设置我的打印方法.在我刚刚在RunPrintWorker方法中调用LabelPrinter.PrintLabels之前.现在我正在重做这个,我无法弄清楚要传递给SizeQueue方法的内容.我应该将新创建的打印文档传递给它吗?

 public class LabelPrinter
{
    private int CurrentCount = 0;

    private List<int> _selectedRows = new List<int>();
    public List<int> SelectedRows
    {
        get { return _selectedRows; }
        set { _selectedRows = value; }
    }

    private string _selectedTemplate;
    public string SelectedTemplate
    {
        get { return _selectedTemplate; }
        set { _selectedTemplate = value; }
    }

    private string _templateDirectory = string.Empty;
    public string TemplateDirectory
    {
        get { return _templateDirectory; }
        set { _templateDirectory = value; }
    }

    public void PrintLabels(PrintDocument printDoc, PageSettings pgSettings, PrinterSettings printerSettings, List<int> selectedRows, string selectedTemplate, string templateDir)
    {
        this._selectedRows = selectedRows;
        this._selectedTemplate = selectedTemplate;
        this._templateDirectory = templateDir;

        printDoc.DefaultPageSettings = pgSettings;
        printDoc.PrinterSettings = printerSettings;

        printDoc.PrinterSettings.MaximumPage = selectedRows.Count();
        printDoc.DefaultPageSettings.PrinterSettings.ToPage = selectedRows.Count();
        printDoc.PrinterSettings.FromPage = 1;

        printDoc.PrintPage += new PrintPageEventHandler(printDoc_PrintPage);

        printDoc.Print();
    }

    private void printDoc_PrintPage(object sender, PrintPageEventArgs e)
    {
        CurrentCount = DrawLabel.DrawLabelsForPrinting(e, SelectedTemplate, SelectedRows, CurrentCount, TemplateDirectory);
    }
}
Run Code Online (Sandbox Code Playgroud)

Aus*_*nen 8

尝试将项添加到队列(例如,Queue<Item>)并让BackgroundWorker处理队列.

编辑:添加一些可能适合您的简单,未经测试的代码.我会用它的处理器封装打印队列,然后发送它的工作.

class SimpleLabelPrinter
{
    public bool KeepProcessing { get; set; }
    public IPrinter Printer { get; set; }

    public SimpleLabelPrinter(IPrinter printer)
    {
        Printer = printer;
    }


    /* For thread-safety use the SizeQueue from Marc Gravell (SO #5030228) */        
    SizeQueue<string> printQueue = new SizeQueue<string>();

    public void AddPrintItem(string item)
    {
        printQueue.Enqueue(item);
    }

    public void ProcessQueue()
    {
        KeepProcessing = true;

        while (KeepProcessing)
        {
            while (printQueue.Count > 0)
            {
                Printer.Print(printQueue.Dequeue());
            }

            Thread.CurrentThread.Join(2 * 1000); //2 secs
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        SimpleLabelPrinter printer1 = new SimpleLabelPrinter(...);
        SimpleLabelPrinter printer2 = new SimpleLabelPrinter(...);

        Thread printer1Thread = new Thread(printer1.ProcessQueue);
        Thread printer2Thread = new Thread(printer2.ProcessQueue);

        //...

        printer1.KeepProcessing = false;  //let the thread run its course...
        printer2.KeepProcessing = false;  //let the thread run its course...
    }
}
Run Code Online (Sandbox Code Playgroud)

SizeQueue实现

编辑2: 解决问题中的更新代码

首先,我将定义一个PrintJob类,其中包含要打印的副本数量以及完整的标签文本或足以导出它的数据(如数据库查询的ID).这将导致您更换SizeQueue<string>在我上面的代码来SizeQueue<PrintJob>以及AddPrintItem(string item)AddPrintJob(PrintJob job).

其次,我会将您的LabelPrinter代码分开(也许创建该IPrinter接口)并将其传递给我的SimpleLabelPrinter的构造函数(此时可能不是最好的名称,但我会让你处理它).

接下来,printer1在适合您的应用程序的任何地方创建LabelPrinter和SimpleLabelPrinter(比如说这个例子)(在你的应用程序Closing或"cleanup"方法中,确保将KeepProcessing设置为false,以便其线程结束).

现在,当您扫描项目时,您将其发送到SimpleLabelPrinter:

printer1.AddPrintJob(new PrintJob(labelText, numberOfCopies));
Run Code Online (Sandbox Code Playgroud)

  • 只有他有多个打印机:) (3认同)
  • 我为此构建了一个系统,并且多个*打印*线程可能会导致比他们解决的问题更多的问题 - 例如,最终用户期望按扫描顺序打印输出.如果您要打印到多台打印机,那么多个打印线程是有意义的. (2认同)

Sim*_*ens 6

基本上你正在做的是说如果打印机忙,用另一个工人覆盖你的printWorker对象并启动那个.然后,您没有引用旧的工作对象,也不进行任何清理.

这有各种各样的问题.

  • 背景工人必须在完成后处理以防止泄漏.
  • 创建更多后台工作并不意味着打印机将以更快的速度运行,这只意味着您有更多工作人员同时尝试访问打印机.

你需要做的是考虑排队.我会说你有两个主要选择.

首先 - 使用ThreadPool.QueueUserWorkItem(...)

ThreadPool.QueueUserWorkItem(new WaitCallback(o=>{printWorker_DoWork();}));
Run Code Online (Sandbox Code Playgroud)

这将使用.net线程池来排队您的任务并在池上处理它们.(池将自动调整大小到适当数量的线程来处理排队的请求).这不需要处理或清理,你只需要点击并忘记,虽然你需要知道工作项目不能保证按照你添加它们的相同顺序处理(很可能会有一些同时处理),所以如果你关心订单,这不好.

第二 - 实现Producer-consumer线程队列模式.这需要一个同步队列,一个线程或一个线程(生产者)添加项目,其他线程(消费者)删除和处理这些项目.如果您不熟悉线程,那么正确起来可能非常棘手,因为您必须确保对共享队列的读/写正确同步,以便维护订单并且不会丢失任何内容.

注意Queue <T>类型,尽管可以使用它,但它本身并不是自动线程安全的.确保如果使用它,则添加一些自己的锁定和同步.

从MSDN页面:

任何实例成员都不保证是线程安全的.

只要未修改集合,Queue <T>)可以同时支持多个读取器.即便如此,通过集合枚举本质上不是一个线程安全的过程.为了在枚举期间保证线程安全,您可以在整个枚举期间锁定集合.要允许多个线程访问集合以进行读写,您必须实现自己的同步.

如果您不关心订单,我会建议更简单的第一个选项,但如果您发现需要对排队和处理进行更细粒度的控制,或者订单很重要,那么您可以转移到更复杂的模式- 如果您按照文章底部的链接进行操作,您将找到示例源代码,或者您可以使用Queue类并确保锁定正确.