How to start a synchronized sequence of events in a GUI thread in C #?

I have an application that should do the following types of things, preferably in the GUI thread, since where most of the action happens and there are no lengthy operations:

Wait 1000 FuncA() Wait 2000 FuncB() Wait 1000 FuncC() 

I understand that I can use the timer with the OnTick function in the style of a standard car, but this seems cumbersome:

  int _state; void OnTick(object sender, EventArgs e) { switch (_state) { case 0: FuncA(); _timer.Interval = TimeSpan.FromSeconds(2); _state = 1; break; case 1: FuncB(); _timer.Interval = TimeSpan.FromSeconds(1); _state = 2; break; case 2: FuncC(); _timer.IsEnabled = false; _state = 0; } } 

Plus I would like to make it generic enough to do something like

 RunSequenceOnGuiThread(new Sequence { {1000, FuncA} {2000, FuncB} {1000, FuncC}}; 

Is there an idiomatic way to do this? Given all the materials of TPL or Rx or even expressions of calculations in F #, I assume that it exists, but I do not find it.

+6
source share
6 answers

Here you can combine a “return on return” and a reactive structure to give you an “asynchronous person”. In principle, it allows you to "wait" for any IObservable. Here I just use it for timers, as it interests you, but you can make it “wait” for the buttons to be pressed (using Subject<Unit> ), etc., before moving on to the next one.

 public sealed partial class Form1 : Form { readonly Executor _executor = new Executor(); public Form1() { InitializeComponent(); _executor.Run(CreateAsyncHandler()); } IEnumerable<IObservable<Unit>> CreateAsyncHandler() { while (true) { var i = 0; Text = (++i).ToString(); yield return WaitTimer(500); Text = (++i).ToString(); yield return WaitTimer(500); Text = (++i).ToString(); yield return WaitTimer(500); Text = (++i).ToString(); } } IObservable<Unit> WaitTimer(double ms) { return Observable.Timer(TimeSpan.FromMilliseconds(ms), new ControlScheduler(this)).Select(_ => Unit.Default); } } public sealed class Executor { IEnumerator<IObservable<Unit>> _observables; IDisposable _subscription = new NullDisposable(); public void Run(IEnumerable<IObservable<Unit>> actions) { _observables = (actions ?? new IObservable<Unit>[0]).Concat(new[] {Observable.Never<Unit>()}).GetEnumerator(); Continue(); } void Continue() { _subscription.Dispose(); _observables.MoveNext(); _subscription = _observables.Current.Subscribe(_ => Continue()); } public void Stop() { Run(null); } } sealed class NullDisposable : IDisposable { public void Dispose() {} } 

This is a small modification to Daniel Ervicker's idea of ​​AsyncIOPipe: http://smellegantcode.wordpress.com/2008/12/05/asynchronous-sockets-with-yield-return-of-lambdas/

+1
source
 Observable.Concat( Observer.Timer(1000).Select(_ => Func1()), Observer.Timer(2000).Select(_ => Func2()), Observer.Timer(1000).Select(_ => Func3())) .Repeat() .Subscribe(); 

The only thing you need to do to make this work is to make sure your Func returns a value (even if it is a Unit.Default value, i.e. nothing)

Edit: Here's how to create a generic version:

 IObservable<Unit> CreateRepeatingTimerSequence(IEnumerable<Tuple<int, Func<Unit>>> actions) { return Observable.Concat( actions.Select(x => Observable.Timer(x.Item1).Select(_ => x.Item2()))) .Repeat(); } 
+10
source

Here's a sketch of this in F #:

 let f() = printfn "f" let g() = printfn "g" let h() = printfn "h" let ops = [ 1000, f 2000, g 1000, h ] let runOps ops = async { for time, op in ops do do! Async.Sleep(time) op() } |> Async.StartImmediate runOps ops System.Console.ReadKey() |> ignore 

This is in a console application, but you can just call runOps in the GUI thread. See also this blog .

If you are using VS11 / NetFx45 / C # 5, you can do the same with C # async / await and List from Tuple Action delegates.

+8
source

using async CTP or .NET 4.5 (C # 5), it REALLY easily uses the async method and the wait statement. This can be called directly in the user interface thread, and it will work as expected.

  public async void ExecuteStuff() { await TaskEx.Delay(1000); FuncA(); await TaskEx.Delay(2000); FuncB(); await TaskEx.Delay(1000); FuncC(); } 
+5
source

Interesting all the different answers. Here is a simple DIY parameter that does not depend on any other libraries and does not require excessive use of stream resources.

Basically, for each action in your list, an onTick function is created that performs this action and then calls DoThings recursively with the rest of the actions and delays.

Here ITimer is just a simple wrapper around DispatcherTimer (but it will work with a SWF timer, or a mock timer for unit testing), and DelayedAction is just a Tuple with int Delay and Action action

 public static class TimerEx { public static void DoThings(this ITimer timer, IEnumerable<DelayedAction> actions) { timer.DoThings(actions.GetEnumerator()); } static void DoThings(this ITimer timer, IEnumerator<DelayedAction> actions) { if (!actions.MoveNext()) return; var first = actions.Current; Action onTick = null; onTick = () => { timer.IsEnabled = false; first.Action(); // ReSharper disable AccessToModifiedClosure timer.Tick -= onTick; // ReSharper restore AccessToModifiedClosure onTick = null; timer.DoThings(actions); }; timer.Tick += onTick; timer.Interval = first.Delay; timer.IsEnabled = true; } } 

If you do not want to delve into F # or the Rx link or use .Net 4.5, this is a simple viable solution.

Here is an example of how to test it:

 [TestClass] public sealed class TimerExTest { [TestMethod] public void Delayed_actions_should_be_scheduled_correctly() { var timer = new MockTimer(); var i = 0; var action = new DelayedAction(0, () => ++i); timer.DoThings(new[] {action, action}); Assert.AreEqual(0, i); timer.OnTick(); Assert.AreEqual(1, i); timer.OnTick(); Assert.AreEqual(2, i); timer.OnTick(); Assert.AreEqual(2, i); } } 

And here are other classes to compile it:

 public interface ITimer { bool IsEnabled { set; } double Interval { set; } event Action Tick; } public sealed class Timer : ITimer { readonly DispatcherTimer _timer; public Timer() { _timer = new DispatcherTimer(); _timer.Tick += (sender, e) => OnTick(); } public double Interval { set { _timer.Interval = TimeSpan.FromMilliseconds(value); } } public event Action Tick; public bool IsEnabled { set { _timer.IsEnabled = value; } } void OnTick() { var handler = Tick; if (handler != null) { handler(); } } } public sealed class MockTimer : ITimer { public event Action Tick; public bool IsEnabled { private get; set; } public double Interval { set { } } public void OnTick() { if (IsEnabled) { var handler = Tick; if (handler != null) { handler(); } } } } public sealed class DelayedAction { readonly Action _action; readonly int _delay; public DelayedAction(int delay, Action action) { _delay = delay; _action = action; } public Action Action { get { return _action; } } public int Delay { get { return _delay; } } } 
+1
source

If you can use C # 4.5 for this, go to the Firoso post: it’s best to do this in C #, which is why Async was created.

However, if you cannot, there may be some ways to do this. I would make a “simple” manager to do this:

 public partial class Form1 : Form { private TimedEventsManager _timedEventsManager; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { _timedEventsManager = new TimedEventsManager(this, new TimedEvent(1000, () => textBox1.Text += "First\n"), new TimedEvent(5000, () => textBox1.Text += "Second\n"), new TimedEvent(2000, () => textBox1.Text += "Third\n") ); } private void button1_Click(object sender, EventArgs e) { _timedEventsManager.Start(); } } public class TimedEvent { public int Interval { get; set; } public Action Action { get; set; } public TimedEvent(int interval, Action func) { Interval = interval; Action = func; } } public class TimedEventsManager { private readonly Control _control; private readonly Action _chain; public TimedEventsManager(Control control, params TimedEvent[] timedEvents) { _control = control; Action current = null; // Create a method chain, beginning by the last and attaching it // the previous. for (var i = timedEvents.Length - 1; i >= 0; i--) { var i1 = i; var next = current; current = () => { Thread.Sleep(timedEvents[i1].Interval); // MUST run it on the UI thread! _control.Invoke(new Action(() => timedEvents[i1].Action())); if (next != null) next(); }; } _chain = current; } public void Start() { new Thread(new ThreadStart(_chain)).Start(); } } 

Beware that this example is specific to Winforms (uses Control.Invoke() ). For WPF, you'll need a slightly different version that uses a thread manager to achieve the same. (if my memory doesn't fail me, you can also use Control.Dispatcher.Invoke (), but keep in mind that this is a different control)

0
source

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


All Articles