以下代码是代表SPA打印的Web API。为简洁起见,我省略了using
语句和实际的打印逻辑。那东西一切正常。兴趣点是通过Web api方法使作业排队将打印逻辑重构到后台线程上。我这样做是因为快速发送的打印作业相互干扰,仅最后一次打印。
它解决了打印作业序列化的问题,但提出了如何检测到关机并发出循环终止信号的问题。
namespace WebPrint.Controllers
{
public class LabelController : ApiController
{
static readonly ConcurrentQueue<PrintJob> queue = new ConcurrentQueue<PrintJob>();
static bool running = true;
static LabelController()
{
ThreadPool.QueueUserWorkItem((state) => {
while (running)
{
Thread.Sleep(30);
if (queue.TryDequeue(out PrintJob job))
{
this.Print(job);
}
}
});
}
public void Post([FromBody]PrintJob job)
{
queue.Enqueue(job);
}
}
public class PrintJob
{
public string url { get; set; }
public string html { get; set; }
public string printer { get; set; }
}
}
考虑到我获取用于服务打印队列的线程的方式,几乎可以肯定它被标记为后台线程,并且应在应用程序池尝试退出时终止,但是我不确定,所以我问你,亲爱的读者,请多多指教您在这种情况下的最佳做法。
好吧,我要寻求最佳实践。
尽管如此,我没有长期运行的后台任务,而有短期运行的任务。它们异步到达不同的线程,但是必须连续地在单个线程上执行,因为WinForms打印方法是为STA线程设计的。
马特·莱特哈吉奇(Matt Lethargic)关于可能失业的观点无疑是一个考虑因素,但是在这种情况下,这并不重要。作业排队的时间绝不会超过几秒钟,丢失只会提示操作员重试。
就此而言,使用消息队列并不能解决“如果有人在使用它时将其关闭的问题”,而只能将其移至另一软件中。许多消息队列不是持久性的,您不会相信我见过有人使用MSMQ解决此问题然后为持久性进行配置而失败的次数。
这非常有趣。
答案 0 :(得分:1)
我将在更高层次上看待您的体系结构,执行诸如打印之类的“长期运行任务”可能应该完全在您的webapi进程之外。
如果我们本人愿意,我会
创建一个包含所有打印逻辑的Windows服务(或您拥有的服务),然后控制器的工作就是通过http或某种队列MSMQ,RabbitMQ,ServiceBus等与该服务进行对话。
如果通过http,则该服务应在内部将打印作业排队,并尽快(在打印发生之前)将200/201返回给控制器,以便控制器可以有效地返回到客户端并释放其资源。
如果通过排队技术,则控制器应将一条消息放在队列中,并尽快尽快返回200/201,然后该服务可以按自己的速率读取消息并一次打印一条消息。
以这种方式进行操作可消除api的开销,并避免在webapi失败的情况下丢失打印作业的可能性(如果api崩溃,则可能/将影响任何后台线程)。另外,如果您在有人打印时进行部署,那么打印作业很有可能失败。
我的2美分价值
答案 1 :(得分:0)
我认为期望的行为不是在Controller内应该执行的操作。
path
可以使用public interface IPrintAgent {
void Enqueue(PrintJob job);
void Cancel();
}
IDependencyResolver
在上述情况下,控制器的唯一工作是将作业排队。
现在从这个角度出发,我将集中讨论问题的主要部分。
正如其他人已经提到的,有很多方法可以实现所需的行为
简单的内存实现看起来像
public class LabelController : ApiController {
private IPrintAgent agent;
public LabelController(IPrintAgent agent) {
this.agent = agent;
}
[HttpPost]
public IHttpActionResult Post([FromBody]PrintJob job) {
if (ModelState.IsValid) {
agent.Enqueue(job);
return Ok();
}
return BadRequest(ModelState);
}
}
,它利用异步事件处理程序在添加作业时按顺序处理队列。
提供了public class DefaultPrintAgent : IPrintAgent {
static readonly ConcurrentQueue<PrintJob> queue = new ConcurrentQueue<PrintJob>();
static object syncLock = new Object();
static bool idle = true;
static CancellationTokenSource cts = new CancellationTokenSource();
static DefaultPrintAgent() {
checkQueue += OnCheckQueue;
}
private static event EventHandler checkQueue = delegate { };
private static async void OnCheckQueue(object sender, EventArgs args) {
cts = new CancellationTokenSource();
PrintJob job = null;
while (!queue.IsEmpty && queue.TryDequeue(out job)) {
await Print(job);
if (cts.IsCancellationRequested) {
break;
}
}
idle = true;
}
public void Enqueue(PrintJob job) {
queue.Enqueue(job);
if (idle) {
lock (syncLock) {
if (idle) {
idle = false;
checkQueue(this, EventArgs.Empty);
}
}
}
}
public void Cancel() {
if (!cts.IsCancellationRequested)
cts.Cancel();
}
static Task Print(PrintJob job) {
//...print job
}
}
,以便可以根据需要使该过程短路。
就像其他用户建议的Cancel
事件一样
Application_End
,或者根据需要手动公开端点。