我需要用户能够扫描一系列项目,并且每个项目都需要打印x个标签。我目前正在尝试使用后台工作程序来完成此操作,但我遇到了一个问题,他们正在如此快速地扫描项目,并且有很多标签要为后台工作者窒息的每个项目打印。这就是我为每次扫描生成后台工作线程的方法,因为在打印大量标签时会发生争用。
private void RunPrintWorker()
{
if (printWorker.IsBusy)
{
printWorker = new BackgroundWorker();
printWorker.DoWork += new DoWorkEventHandler(printWorker_DoWork);
printWorker.RunWorkerAsync();
}
else
printWorker.RunWorkerAsync();
}
我没有得到后台工作者的任何例外,它似乎没有足够快地创建线程。我是使用多线程的新手,所以有人能指出我做错的方向吗?
感谢。
编辑:感谢大家的建议和阅读材料,这应该真的有帮助。打印标签的顺序并不重要,因为它们扫描速度非常快,标签也只打印到一台打印机上。我将在实施完成并运行后标记答案。 编辑:奥斯汀,下面是我如何设置我的打印方法。在我刚刚在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);
}
}
答案 0 :(得分: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...
}
}
编辑2 :解决问题中的更新代码
首先,我将定义一个PrintJob
类,其中包含要打印的副本数量以及完整的标签文本或足以导出它的数据(如数据库查询的ID)。这会导致您将上面代码中的SizeQueue<string>
替换为SizeQueue<PrintJob>
以及AddPrintItem(string item)
替换为AddPrintJob(PrintJob job)
。
其次,我会将您的LabelPrinter代码分开(也许创建该IPrinter接口)并将其传递给我的SimpleLabelPrinter的构造函数(此时可能不是最好的名称,但我会让你处理它)。 / p>
接下来,在适合您的应用的任何地方创建您的LabelPrinter和SimpleLabelPrinter(例如printer1
)(在您的应用关闭或“清理”方法中,确保将KeepProcessing设置为false以使其线程结束)
现在,当您扫描项目时,您将其发送到SimpleLabelPrinter:
printer1.AddPrintJob(new PrintJob(labelText, numberOfCopies));
答案 1 :(得分:6)
基本上你正在做的是说如果打印机忙,用另一个工人覆盖你的printWorker对象并启动它。然后,您没有引用旧的工作对象,也不进行任何清理。
这有各种各样的问题。
你需要做的是考虑排队。我想说你有两个主要选择。
首先 - 使用ThreadPool.QueueUserWorkItem(...)
ThreadPool.QueueUserWorkItem(new WaitCallback(o=>{printWorker_DoWork();}));
这将使用.net线程池来排队您的任务并在池上处理它们。 (池将自动调整大小到适当数量的线程来处理排队的请求)。这不需要处理或清理,你只需要点击并忘记,虽然你需要知道工作项目不能保证按照你添加它们的相同顺序处理(很可能会有一些同时处理),所以如果你关心订单,这不好。
第二 - 实施Producer-consumer thread queue pattern。这需要一个同步队列,一个线程或一个线程(生产者)添加项目,其他线程(消费者)删除和处理这些项目。如果您不熟悉线程,那么要做到正确是非常棘手的,因为您必须确保对共享队列的读/写正确同步,以便维护订单并且不会丢失任何内容。
注意Queue<T>类型,尽管可以使用它,但它本身并不是自动线程安全的。确保如果你使用它,你可以添加一些自己的锁定和同步。
从MSDN页面:
不保证所有实例成员都是线程安全的。
队列&lt; T&gt;)可以同时支持多个阅读器,只要不修改集合即可。即便如此,通过集合枚举本质上不是一个线程安全的过程。为了在枚举期间保证线程安全,您可以在整个枚举期间锁定集合。要允许多个线程访问集合以进行读写,您必须实现自己的同步。
如果您不关心订单,我会建议更简单的第一个选项,但如果您发现需要对排队和处理进行更细粒度的控制,或者订单很重要,那么您可以转到更复杂的模式 - 如果您按照文章底部的链接进行操作,您会发现examples和source code,或者您可以使用Queue类并确保锁定正确。
答案 2 :(得分:0)
您需要实施排队系统。查看Queue类,并将PrintDocuments排入其中。启动后台线程(在这种情况下不是后台工作者)并定期检查队列以查找更多项目并在可能的情况下打印它们。
在backgroundworker中运行这样的方法:
private void PrintLoop()
{
while (true)
{
if (documents.Count > 0)
{
PrintDocument document = documents.Dequeue();
document.Print();
}
else
{
Thread.Sleep(1000);
}
}
}
每台打印机制作一个BackgroundWorker,并将每个打印机打印到不同的打印机。