代码之家  ›  专栏  ›  技术社区  ›  Peter Wone

如何从WebAPI后台服务中退出Clean

  •  2
  • Peter Wone  · 技术社区  · 6 年前

    下面的代码是一个代表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线程设计的。

    MattLethargic关于可能失业的观点当然是一个考虑因素,但对于这个案例来说,这并不重要。作业不会排队超过几秒钟,丢失只会提示操作员重试。

    因此,使用消息队列并不能解决“如果有人在使用时关闭它会怎样”的问题,它只是将它移动到另一个软件。很多消息队列不是持久性的,您不会相信我看到有人使用msmq来解决这个问题,然后未能将其配置为持久性的次数。

    这很有趣。

    http://thecodelesscode.com/case/156

    2 回复  |  直到 6 年前
        1
  •  0
  •   Nkosi    6 年前

    我相信期望的行为不是应该在控制器内完成的事情。

    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 其他用户建议的事件

    var agent = new DefaultPrintAgent();
    agent.Cancel();
    

    或者,如果需要的话,通过公开一个端点来手动操作。

        2
  •  1
  •   matt_lethargic    6 年前

    我将在更高的层次上研究您的体系结构,执行“长时间运行的任务”(如打印)可能完全不在您的WebAPI过程中。

    如果我们自己这样做,我会:

    创建一个包含所有打印逻辑的Windows服务(或您拥有的服务),然后控制器的任务就是通过HTTP或某种队列msmq、rabbitmq、servicebus等与该服务进行对话。

    如果通过HTTP,那么服务应该在内部对打印作业进行排队,并尽快(在打印发生之前)将200/201返回到控制器,以便控制器能够高效地返回到客户机并释放其资源。

    如果通过排队技术,那么控制器应该在队列上放置一条消息,然后尽快返回200/201,然后服务可以以自己的速率读取消息,并一次打印一条消息。

    这样做可以减少API的开销,并且在WebAPI失败的情况下也可以减少打印作业的丢失(如果API崩溃,任何后台线程都可能/将受到影响)。另外,如果在某人打印时进行部署,打印作业很可能会失败。

    我的2美分价值

    推荐文章