В предыдущей статье я привел пример функции-итератора, исполняющейся по событию от DispatcherTimer. Мне стало интересно развить эту идею дальше. Несколько таких функций с независимыми таймерами образуют систему с кооперативной многозадачностью. Далее будем называть такие функции ко-итераторами.
Ко-итераторы выполняются в главном потоке программы, поэтому их общение как между собой,
так и с элементами пользовательского интерфейса, не требует применения средств межпоточного взаимодействия.
Как и async/await, ко-итераторы позволяют в императивном стиле описывать логику последовательного выполнения действий с ожиданием, но отличаются тем, что допускают широкую модификацию своего поведения с помощью декораторов, которые вступают в действие не только до- и после вызова, но и на каждой итерации.
Возьмем традиционную задачу долго выполняемого действия с отображением хода выполнения и возможностью прерывания. Стандартным решением будет такой метод:
Мы же доверим прерывание вызывающему коду, а информацию о прогрессе будем возвращать, как результат итерации.
Рассмотрим вызов ко-итератора из ко-итератора.
Примем соглашение, что в таких случаях будем возвращать непосредственно итератор:
А о разворачивании позаботится следующий метод:
Теперь можно написать несколько декораторов.
Выполнить молча:
Выполнить с таймаутом:
Выполнять реже/чаще:
Ждать условия:
В заключение, код объекта, исполняющего вышеописанные ко-итераторы,
Ко-итераторы выполняются в главном потоке программы, поэтому их общение как между собой,
так и с элементами пользовательского интерфейса, не требует применения средств межпоточного взаимодействия.
Как и async/await, ко-итераторы позволяют в императивном стиле описывать логику последовательного выполнения действий с ожиданием, но отличаются тем, что допускают широкую модификацию своего поведения с помощью декораторов, которые вступают в действие не только до- и после вызова, но и на каждой итерации.
Возьмем традиционную задачу долго выполняемого действия с отображением хода выполнения и возможностью прерывания. Стандартным решением будет такой метод:
public struct ProgressInfo {
public string Message;
public int Current, Total;
}
async Task Foo(IProgress<ProgressInfo> progress, CancellationToken ct)
Мы же доверим прерывание вызывающему коду, а информацию о прогрессе будем возвращать, как результат итерации.
public static IEnumerable<object> Process<T>(this ICollection<T> collection, Action<T> action) {
ProgressInfo info = new ProgressInfo() { Total = collection.Count };
foreach (var item in collection) {
action(item);
info.Current++;
info.Message = string.Format("Processed {0:d} from {1:d}", info.Current, info.Total);
yield return info;
}
yield break;
}
Рассмотрим вызов ко-итератора из ко-итератора.
IEnumerable<object> Foo() { yield break; }
IEnumerable<object> Bar() { yield break; }
IEnumerable<object> Baz() {
foreach (var a in Foo()) yield return a;
foreach (var b in Bar()) yield return b;
yield break;
}
Примем соглашение, что в таких случаях будем возвращать непосредственно итератор:
IEnumerable<object> Baz() {
yield return Foo();
yield return Bar();
yield break;
}
А о разворачивании позаботится следующий метод:
public static IEnumerable<object> Flat(this IEnumerable<object> cot) {
foreach (var a in cot) {
var ea = a as IEnumerable<object>;
if (ea==null) yield return a;
else {
foreach (var b in ea.Flat()) yield return b;
}
}
yield break;
}
Теперь можно написать несколько декораторов.
Выполнить молча:
public static void Execute(this IEnumerable<object> cot) {
foreach (var a in cot.Flat()) { }
}
Выполнить с таймаутом:
public class ValueOf<T> {
public T Value;
}
public static IEnumerable<object> Timeout(this IEnumerable<object> cot,
TimeSpan duration, ValueOf<bool> 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<object> Rate(this IEnumerable<object> 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<object> Wait(Func<bool> condition) {
while (!condition()) yield return null;
yield break;
}
В заключение, код объекта, исполняющего вышеописанные ко-итераторы,
который не столь интересен.
public class TimerThread {
bool _IsCancelled, _IsCompleted;
DispatcherTimer Timer;
IEnumerator<object> Enumerator;
public event Action<ProgressInfo> Progress;
public TimerThread(IEnumerable<object> 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; } }
}