Как выйти чистый из фонового сервиса WebAPI - PullRequest
0 голосов
/ 19 ноября 2018

Код ниже представляет собой веб-API, который печатается от имени SPA.Для краткости я опустил операторы using и реальную логику печати.Это все работает отлично.Интерес представляет рефакторинг логики печати в фоновом потоке с методом веб-API, ставящим задачу в очередь.Я сделал это, потому что задания на печать, отправленные в быстрой последовательности, мешали друг другу только с последней печатью задания.

Это решает проблему сериализации заданий на печать, но поднимает вопрос о том, как обнаружить отключение и подать сигнал петле.terminate.

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.

Мэтт Летаргик о возможной потере работы, безусловно, является соображением, но для этого случая это не имеет значения.Задания никогда не ставятся в очередь дольше, чем на несколько секунд, и потеря просто повлечет за собой попытку оператора.

В этом отношении использование очереди сообщений не решает проблему «что если кто-то отключит ее во время ее использования», а просто перемещает ее в другое программное обеспечение.Многие очереди сообщений не являются постоянными, и вы не поверите, сколько раз я видел, как кто-то использовал MSMQ для решения этой проблемы, а затем не смог настроить ее на постоянство.

Это было оченьинтересно.

http://thecodelesscode.com/case/156

Ответы [ 2 ]

0 голосов
/ 21 ноября 2018

Я считаю, что желаемое поведение не то, что должно быть сделано в контроллере.

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();

или вручную, выставив конечную точку, если это необходимо.

0 голосов
/ 20 ноября 2018

Я бы посмотрел на вашу архитектуру на более высоком уровне, поскольку выполнение «долго выполняемых задач», таких как печать, вероятно, должно полностью происходить вне вашего процесса webapi.

Если бы это мы сами, я бы:

Создайте службу Windows (или что у вас), в которой есть вся логика печати, тогда задача контроллера состоит в том, чтобы просто общаться с этой службой либо по http, либо через какую-то очередь MSMQ, RabbitMQ, ServiceBus и т. Д.

Если через http, то служба должна внутренне поставить в очередь задания на печать и вернуть 200/201 на контроллер как можно скорее (до того, как произойдет печать), чтобы контроллер мог эффективно вернуться к клиенту и освободить его ресурсы.

Если с помощью технологии очередей контроллер должен поместить сообщение в очередь и снова вернуть 200/201 как можно быстрее, служба может затем прочитать сообщения со своей собственной скоростью и распечатать по одному за раз.

Делая это таким образом, вы снимаете накладные расходы с вашего API, а также с возможностью потери заданий на печать в случае сбоя в webapi (в случае сбоя API любые фоновые потоки могут / будут иметь место). Кроме того, если вы выполняете развертывание в точке, где кто-то печатает, есть большая вероятность, что задание на печать не будет выполнено.

Мои 2 цента стоят

...