Asp.net web api 如何从WebAPI后台服务退出clean

Asp.net web api 如何从WebAPI后台服务退出clean,asp.net-web-api,threadpool,Asp.net Web Api,Threadpool,下面的代码是代表SPA打印的Web API。为简洁起见,我省略了使用语句和实际打印逻辑的。这些东西都很好用。有趣的是将打印逻辑重构到后台线程上,使用WebAPI方法将作业排队。我这样做是因为快速连续发送的打印作业仅在最后一次打印作业时相互干扰 它解决了序列化打印作业的问题,但提出了如何检测关闭并向循环发送终止信号的问题 namespace WebPrint.Controllers { public class LabelController : ApiController {

下面的代码是代表SPA打印的Web API。为简洁起见,我省略了使用语句和实际打印逻辑的
。这些东西都很好用。有趣的是将打印逻辑重构到后台线程上,使用WebAPI方法将作业排队。我这样做是因为快速连续发送的打印作业仅在最后一次打印作业时相互干扰

它解决了序列化打印作业的问题,但提出了如何检测关闭并向循环发送终止信号的问题

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; }
    }
}
namespace WebPrint.Controllers
{
公共类LabelController:ApicController
{
静态只读ConcurrentQueue队列=新ConcurrentQueue();
静态布尔运行=真;
静态标签控制器()
{
ThreadPool.QueueUserWorkItem((状态)=>{
(跑步时)
{
睡眠(30);
if(queue.TryDequeue(输出打印作业))
{
这个。打印(作业);
}
}
});
}
公共作废帖子([FromBody]打印作业)
{
排队。排队(作业);
}
}
公共类打印作业
{
公共字符串url{get;set;}
公共字符串html{get;set;}
公共字符串打印机{get;set;}
}
}
考虑到我获取一个线程来为打印队列提供服务的方式,它几乎肯定会被标记为后台线程,并且在应用程序池尝试退出时应该终止,但我不确定这一点,所以亲爱的读者们,我向你们请教在这种情况下的最佳实践的集体概念


嗯,我确实要求最佳实践

然而,我没有长时间运行的后台任务,我有短时间运行的任务。它们异步到达不同的线程,但必须在单个线程上串行执行,因为WinForms打印方法是为STA线程设计的

马特·莱哈尔奇关于可能失业的观点当然是一个考虑因素,但在这种情况下,这并不重要。作业的排队时间不会超过几秒钟,丢失只会提示操作员重试

就这一点而言,使用消息队列并不能解决“如果有人在使用它时关闭它会怎么样”的问题,它只是将它移动到另一个软件中。很多消息队列都不是持久性的,你不会相信有多少次我看到有人使用MSMQ来解决这个问题,然后又无法将其配置为持久性

这很有趣


我将从更高的层次来审视您的体系结构,执行“长时间运行的任务”,如打印,可能完全不在您的webapi过程中进行

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

创建一个包含所有打印逻辑的windows服务(或您所拥有的),然后控制器的工作就是通过http或某种队列MSMQ、RabbitMQ、ServiceBus等与该服务通信

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

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

这样做可以减少api的开销,也可以在webapi出现故障时(如果api崩溃,任何后台线程都可能/将受到影响)丢失打印作业的可能性。另外,如果您在有人打印时进行部署,打印作业很可能会失败


我的2美分价值

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

public interface IPrintAgent {
    void Enqueue(PrintJob job);
    void Cancel();
}
可以使用框架
idependencysolver

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
    }
}

或者,如果需要的话,通过暴露端点来手动操作。

应审查此设计。控制器似乎做得太多了。以这种方式尝试使用后台线程最终也会导致问题。创建一个独立于控制器的服务来管理队列和打印。在上述场景中,控制器的唯一任务是将作业排队使用并经常检查。在Global.asax中,我们有
Application\u End
事件。考虑到整个shebang不到80 LoC,而web方法只有一行体,我认为分离部分不会降低复杂性。这里列出了许多选项,例如,使用QueueBackgroundWorkItem OP不愿意接受干净的设计建议,这让我想起一句话:如果你不花时间把它做好,你什么时候有时间再做一遍?@Nkosi,我在打印标签标签,而不是40页的文档。它们打印时间为亚秒,但我会以两三次的间隔发送它们。我只得到最后一个,直到我使用队列序列化它们。队列的要点不是异步性,而是事务的序列化。问题的关键是,当有人想要停止应用程序池时,如何向队列轮询循环发出退出信号。@PeterWone然后将队列与async await一起使用,应该可以获得所需的顺序行为。这也将很好地与另一个建议的取消令牌配合使用poster@PeterWone我仍然认为理想的行为
var agent = new DefaultPrintAgent();
agent.Cancel();