Фоновый работник Singleton находится в тупике внутри таймераC#

Место общения программистов C#
Ответить
Anonymous
 Фоновый работник Singleton находится в тупике внутри таймера

Сообщение Anonymous »


У меня есть потребитель, пользовательский фоновый рабочий процесс, использующий BlockingCollection, подобный этому

публичный класс PCQueue: IDisposable { BlockingCollection _taskQ = new BlockingCollection(); общественный PCQueue () { Task.Factory.StartNew (Consume); } общественный недействительный Dispose () { _taskQ.CompleteAdding (); } public void EnqueueTask (Действие) => _taskQ.Add (действие); void Consume() { foreach (Действие в _taskQ.GetConsumingEnumerable()) action(); } } Затем я хочу убедиться, что у меня есть только один фоновый поток или задача, созданная этим исполнителем для каждого приложения, поэтому я использую оболочку для создания синглтона для этого исполнителя только один раз.

публичный класс InstanceService где T: new() { частный статический только для чтения T _instance = new(); общественный статический экземпляр T => _instance; статический экземплярService(){} частный экземплярService() {} } Затем я использую его в коде, чтобы начать планировать некоторую работу. У меня несколько продюсеров.
вар планировщик = InstanceService
.Instance; Scheduler.EnqueueTask(() => ConsoleWriteLine("1")); Scheduler.EnqueueTask(() => ConsoleWriteLine("2")); Scheduler.EnqueueTask(() => ConsoleWriteLine("3")); Кроме того, существует периодический производитель, таймер, планирующий некоторые задачи параллельно.

var интервал = новый таймер(TimeSpan.FromSeconds(1)); вар планировщик = InstanceService.Instance; интервал.Включено = правда; интервал.Elapsed += (отправитель, e) => Scheduler.EnqueueTask(() => ConsoleWriteLine("4")); Проблема

Цифра «4», вызываемая таймером, никогда не выводится. Действие, которое должно его распечатать, корректно добавляется в очередь задач, но GetConsumingEnumerable() никогда не запускает это действие. Ни в одной из этих задач нет тяжелых операций, только Console.WriteLine(), но кажется, что что-то блокирует блокирующую коллекцию, и это происходит ТОЛЬКО внутри таймера. Я могу планировать новые задачи вне таймера в любое время, и они работают нормально. Как сделать так, чтобы и обычные производители, и таймер выполняли задачи в одном фоновом воркере без блокировки, и почему блокировка происходит именно сейчас?

Уже пробовал
[*]Пытался использовать как долго выполняющиеся Task, так и Thread [*]Пыталась создать поток внутри EnqueueTask по требованию вместо конструктора, чтобы убедиться, что он всегда доступен. [*]Пыталась сохранить эту цепочку в статической переменной, чтобы гарантировать, что она будет создана только один раз. Пыталась заменить BlockingCollection на ConcurrentQueue, Queue с блокировками и циклом while и Каналы
Обновить

Еще один способ воспроизвести без таймера. Проблема в том, что блокирующий сбор не переходит к следующей задаче в очереди, если она не ожидается. Другими словами, функция «выстрелил и забыл» не работает. В приведенном ниже коде второй вызов застревает в очереди до тех пор, пока не будет ожидан следующий.

public static async Task Main() { // Это напечатает «Начало» вар originalScheduler = InstanceService.Instance; await originalScheduler.Send(() => Console.WriteLine("Start")).Task; // Если не ждать, ничего не напечатается originalScheduler.Send(() => Console.WriteLine("Продолжить")); // Это напечатает два последних сообщения: «Продолжить» и «Завершить». ожидайте originalScheduler.Send(() => Console.WriteLine("Завершено")).Task; } Небольшое обновление планировщика, который должен возвращать TaskCompletionSource, чтобы производитель мог отменить или дождаться результата задачи.
публичный класс PCQueue: IDisposable { BlockingCollection _taskQ = new BlockingCollection(); общественный PCQueue () { Task.Factory.StartNew (Consume); } общественный недействительный Dispose () { _taskQ.CompleteAdding (); } public TaskCompletionSource EnqueueTask (действие Func) { var завершение = новый TaskCompletionSource(); _taskQ.Add (() => завершение.TrySetResult(действие())); завершение возврата; // Возможно, сюда нужно вернуть задачу } void Consume() { foreach (Действие в _taskQ.GetConsumingEnumerable()) action(); } }
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»