Я пытаюсь использовать Reactive Extensions (Rx) для обработки потока данных. Однако обработка каждого элемента может занять некоторое время. Чтобы разбить обработку, я использую CancellationToken
, который фактически останавливает подписку.
Когда была запрошена отмена, как мне изящно завершить текущую работу и закончить, не теряя при этом никаких данных?
Пример:
var cts = new CancellationTokenSource();
cts.Token.Register(() => Console.WriteLine("Token cancelled."));
var observable = Observable
.Interval(TimeSpan.FromMilliseconds(250));
observable
.Subscribe(
value =>
{
Console.WriteLine(value);
Thread.Sleep(500);
if (cts.Token.IsCancellationRequested)
{
Console.WriteLine("Cancellation detected on {0}.", value);
Thread.Sleep(500);
Console.WriteLine("Cleaning up done for {0}.", value);
}
},
() => Console.WriteLine("Completed"),
cts.Token);
Console.ReadLine();
cts.Cancel();
Console.WriteLine("Job terminated.");
Выход:
0
1
2
Token cancelled.
Job terminated.
Cancellation detected on 2.
Cleaning up done for 2.
Как видно из вывода, строка "Job terminated" не является последней строкой, а это означает, что у очистки не было бы достаточно времени для завершения до того, как приложение завершится.
Используя ответ на аналогичный вопрос вместе с ответом на вопрос о ожидании перед завершением, я выяснил решение, которое делает то, что я хочу.
Моя первоначальная проблема заключалась в том, что я не нашел возможности ждать потока подписки. Ответы, приведенные выше, привели меня к рефакторингу кода тремя способами:
Я переместил логику отмены от подписки на наблюдаемую.
Подписка завершается в свою собственную Task
(поэтому выполнение может продолжаться ReadLine
-statement).
Для управления стратегией выхода из приложения был введен ManualResetEvent
.
Решение:
var reset = new ManualResetEvent(false);
var cts = new CancellationTokenSource();
cts.Token.Register(() => Console.WriteLine("Token cancelled."));
var observable = Observable
.Interval(TimeSpan.FromMilliseconds(250))
.TakeWhile(x => !cts.Token.IsCancellationRequested)
.Finally(
() =>
{
Console.WriteLine("Finally: Beginning finalization.");
Thread.Sleep(500);
Console.WriteLine("Finally: Done with finalization.");
reset.Set();
});
await Task.Factory.StartNew(
() => observable
.Subscribe(
value =>
{
Console.WriteLine("Begin: {0}", value);
Thread.Sleep(2000);
Console.WriteLine("End: {0}", value);
},
() => Console.WriteLine("Completed: Subscription completed.")),
TaskCreationOptions.LongRunning);
Console.ReadLine();
cts.Cancel();
reset.WaitOne();
Console.WriteLine("Job terminated.");
Выход:
Begin: 0
End: 0
Begin: 1
Token cancelled.
End: 1
Completed: Subscription completed.
Finally: Beginning finalization.
Finally: Done with finalization.
Job terminated.
Будучи совершенно новым для Reactive Extensions, я не знаю, является ли это лучшим решением для моей проблемы. Но это отличный пример для примера, поставленного в вопросе, поскольку он удовлетворяет моим требованиям:
ManualResetEvent
).TakeWhile
-method.Finally
-method.Это гораздо более приятное решение.
Если я правильно понял вопрос, это не проблема Rx, это "Что бы вы ни делали в проблеме" Подписаться ". Действие вашей подписки занимает полсекунды, а возможность очистки занимает еще полсекунды, а ваше завершение работы занимает микросекунды. Что вы хотите сжать между отменой и прекращением?
Лучшим советом, который я могу вам дать, является то, что действие подписки соблюдает маркер отмены лучше, чем призывы Thread.Sleep
.