В предыдущей статье я привел пример функции-итератора, исполняющейся по событию от DispatcherTimer. Мне стало интересно развить эту идею дальше. Несколько таких функций с независимыми таймерами образуют систему с кооперативной многозадачностью. Далее будем называть такие функции ко-итераторами.
Ко-итераторы выполняются в главном потоке программы, поэтому их общение как между собой,
так и с элементами пользовательского интерфейса, не требует применения средств межпоточного взаимодействия.
Как и async/await, ко-итераторы позволяют в императивном стиле описывать логику последовательного выполнения действий с ожиданием, но отличаются тем, что допускают широкую модификацию своего поведения с помощью декораторов, которые вступают в действие не только до- и после вызова, но и на каждой итерации.
Возьмем традиционную задачу долго выполняемого действия с отображением хода выполнения и возможностью прерывания. Стандартным решением будет такой метод:
public struct ProgressInfo {
public string Message;
public int Current, Total;
}
async Task Foo(IProgress progress, CancellationToken ct)
Мы же доверим прерывание вызывающему коду, а информацию о прогрессе будем возвращать, как результат итерации.
public static IEnumerable
Рассмотрим вызов ко-итератора из ко-итератора.
IEnumerable
Примем соглашение, что в таких случаях будем возвращать непосредственно итератор:
public static IEnumerable Flat(this IEnumerable cot) {
foreach (var a in cot) {
var ea = a as IEnumerable;
if (ea==null) yield return a;
else {
foreach (var b in ea.Flat()) yield return b;
}
}
yield break;
}
Теперь можно написать несколько декораторов.
Выполнить молча:
public static void Execute(this IEnumerable cot) {
foreach (var a in cot.Flat()) { }
}
Выполнить с таймаутом:
public class ValueOf {
public T Value;
}
public static IEnumerable Timeout(this IEnumerable cot,
TimeSpan duration, ValueOf timeout) {
var limit = DateTimeOffset.Now+duration;
foreach (var a in cot.Flat()) {
if (DateTimeOffset.Now>limit) {
timeout.Value = true;
break;
}
yield return a;
}
yield break;
}
Выполнять реже/чаще:
public static IEnumerable Rate(this IEnumerable cot, double rate) {
double summ = 0.001;
foreach (var a in cot.Flat()) {
summ += 1.0;
while (summ>rate) { summ -= rate; yield return a; }
}
yield break;
}
Ждать условия:
public static IEnumerable Wait(Func condition) {
while (!condition()) yield return null;
yield break;
}
В заключение, код объекта, исполняющего вышеописанные ко-итераторы,
который не столь интересен.
public class TimerThread {
bool _IsCancelled, _IsCompleted;
DispatcherTimer Timer;
IEnumerator Enumerator;
public event Action Progress;
public TimerThread(IEnumerable cot, double interval) {
Enumerator = cot.Flat().GetEnumerator();
Timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(interval) };
Timer.Tick += Timer_Tick;
}
void Timer_Tick(object sender, EventArgs ea) {
if (!Enumerator.MoveNext()) {
_IsCompleted = true;
Timer.IsEnabled = false;
} else if (Enumerator.Current is ProgressInfo) {
if (Progress!=null) Progress((ProgressInfo)Enumerator.Current);
}
}
public bool IsEnabled {
get { return Timer.IsEnabled; }
set {
if (_IsCancelled || _IsCompleted) return;
Timer.IsEnabled = value;
}
}
public bool IsCancelled {
get { return _IsCancelled; }
set {
if (!value) return;
_IsCancelled = true;
Timer.IsEnabled = false;
}
}
public bool IsCompleted { get { return _IsCompleted; } }
}
Комментарии (2)
shai_hulud
15 ноября 2016 в 10:48
+1
↑
↓
А чем не устраивает обычные .NET средства, которое уже созданы для решения этой задачи?
async/await, Task.Delay, IProgress[T]?
Пример:
public static Task Progress(IProgress progress)
{
var iteration = 0;
var delay = TimeSpan.FromSeconds(1);
while(true)
{
await Task.Delay(delay);
progress.Report(new ProgressInfo(iteration, "iteration: " + iteration))
iteration++;
}
}
public static Task Timeout(this Task anotherTask, TimeSpan timeout)
{
var timeoutTask = Task.Delay(timeout);
if (await Task.WhenAny(anotherTask, timeoutTask) == timeoutTask)
throw new TimeoutException();
}
AmirYantimirov
15 ноября 2016 в 10:56
(комментарий был изменён)
0
↑
↓
Не могу сказать, что не устраивает. Я еще не применял ни обычные средства, ни ко-итераторы для решения реальных задач. Только прикидывал возможности.