如何从WebAPI后台服务中退出清理工作

时间:2018-11-18 22:12:12

标签: asp.net-web-api threadpool

以下代码是代表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解决此问题然后为持久性进行配置而失败的次数。

这非常有趣。

http://thecodelesscode.com/case/156

2 个答案:

答案 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

,或者根据需要手动公开端点。