Ко-итераторы на таймерах

от автора

В предыдущей статье я привел пример функции-итератора, исполняющейся по событию от DispatcherTimer. Мне стало интересно развить эту идею дальше. Несколько таких функций с независимыми таймерами образуют систему с кооперативной многозадачностью. Далее будем называть такие функции ко-итераторами.

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

Как и 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; } }   } 

ссылка на оригинал статьи https://habrahabr.ru/post/315280/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *