Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does one kick off a timed sequence of events on the GUI thread in C#?

I've got an app that has to do the following type of things, preferably on the GUI thread since that's where most of the action is taking place and there's no long-running ops:

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

I realize I could use a timer with a state-machine style OnTick function, but that 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'd like to be able 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 kind of thing? Given all the TPL stuff, or Rx, or even the computation expressions in F# I'd assume one exists, but I'm not finding it.

like image 332
lobsterism Avatar asked Mar 23 '12 19:03

lobsterism


3 Answers

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

The only thing you have to do to make this work, is make sure that your Func's return a value (even if that value is Unit.Default, i.e. nothing)

Edit: Here's how to make 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();
}
like image 162
Ana Betts Avatar answered Nov 18 '22 00:11

Ana Betts


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

That's in a console app, but you can just call runOps on the GUI thread. See also this blog.

If you're using VS11/NetFx45/C#5, you can do a similar thing with C# async/await and a List of Tuple of Action delegates.

like image 8
Brian Avatar answered Nov 17 '22 23:11

Brian


using the async CTP or .NET 4.5 (C# 5) it's REALLY easy using an async method and the await operator. This can be called directly on the UI 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();
    }
like image 5
Firoso Avatar answered Nov 18 '22 00:11

Firoso