📜 ⬆️ ⬇️

Co-iterators on timers

In the previous article, I gave an example of an iterator function that executes on an event from DispatcherTimer . It became interesting to me to develop this idea further. Several of these functions with independent timers form a system with cooperative multitasking. Further we will call such functions co-iterators.

Co-iterators are executed in the main thread of the program, therefore their communication is like between themselves,
and with elements of the user interface does not require the use of inter-thread communication tools.

Like async / await , co-iterators allow imperative style to describe the logic of sequential execution of actions with expectation, but differ in that they allow wide modification of their behavior using decorators that come into effect not only before and after the call, but also on each iteration.

Take the traditional task of a long-running action with the display of progress and the possibility of interruption. The standard solution would be this method:
')
public struct ProgressInfo { public string Message; public int Current, Total; } async Task Foo(IProgress<ProgressInfo> progress, CancellationToken ct) 

We will entrust the interruption to the calling code, and we will return information about the progress as a result of the iteration.

  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; } 

Consider calling a co-iterator from a co-iterator.

  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; } 

We accept the agreement that in such cases we will return the iterator directly:

  IEnumerable<object> Baz() { yield return Foo(); yield return Bar(); yield break; } 

And the following method will take care of the deployment:

  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; } 

Now you can write a few decorators.

Run silently:

  public static void Execute(this IEnumerable<object> cot) { foreach (var a in cot.Flat()) { } } 

Run with timeout:

  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; } 

Run less / more often:

  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; } 

Wait conditions:

  public static IEnumerable<object> Wait(Func<bool> condition) { while (!condition()) yield return null; yield break; } 

Finally, the code of the object executing the above described iterators,
which is not so interesting.
  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; } } } 

Source: https://habr.com/ru/post/315280/


All Articles