如何在C#中的GUI线程上启动定时事件序列?

lob*_*ism 6 c# f# asynchronous task-parallel-library system.reactive

我有一个应用程序必须执行以下类型的操作,最好是在GUI线程上,因为这是大多数操作发生的地方,并且没有长时间运行的操作:

Wait 1000
FuncA()
Wait 2000
FuncB()
Wait 1000
FuncC()
Run Code Online (Sandbox Code Playgroud)

我意识到我可以使用具有状态机样式OnTick功能的计时器,但这似乎很麻烦:

    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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

另外,我希望能够使它足够通用,以便做类似的事情

RunSequenceOnGuiThread(new Sequence {
    {1000, FuncA}
    {2000, FuncB}
    {1000, FuncC}};
Run Code Online (Sandbox Code Playgroud)

有没有惯用的方法来做这种事情?鉴于所有的TPL内容,或者Rx,甚至F#中的计算表达式,我都假设存在,但我找不到它.

Ana*_*tts 10

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

要做到这一点,你唯一需要做的就是确保你的Func返回一个值(即使该值是Unit.Default,即没有)

编辑:以下是如何制作通用版本:

IObservable<Unit> CreateRepeatingTimerSequence(IEnumerable<Tuple<int, Func<Unit>>> actions)
{
    return Observable.Concat(
        actions.Select(x => 
            Observable.Timer(x.Item1).Select(_ => x.Item2())))
        .Repeat();
}
Run Code Online (Sandbox Code Playgroud)

  • 可能值得一提的是,对于不知情的人来说这是通过[Reactive Extensions](http://msdn.microsoft.com/en-us/data/gg577609) (4认同)
  • 不会"做"比那里的"选择"更好吗?这样您就不必担心函数的返回值. (3认同)
  • @BryanAnderson可能,但我每次使用时都感觉很糟糕:) (2认同)

Bri*_*ian 8

这是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
Run Code Online (Sandbox Code Playgroud)

这是在控制台应用程序中,但您可以在GUI线程上调用runOps.另见此博客.

如果您使用VS11/NetFx45/C#5,你可以做类似的事情用C#async/ awaitListTupleAction代表.


Fir*_*oso 5

使用异步CTP或.NET 4.5(C#5),使用异步方法和await运算符非常容易.这可以直接在UI线程上调用,它将按预期工作.

    public async void ExecuteStuff()
    {
        await TaskEx.Delay(1000);
        FuncA();
        await TaskEx.Delay(2000);
        FuncB();
        await TaskEx.Delay(1000);
        FuncC();
    }
Run Code Online (Sandbox Code Playgroud)


Dax*_*ohl 1

这是一种将“收益回报”和反应式框架结合起来的方法,为您提供“穷人的异步”。基本上让你“等待”任何 IObservable。在这里,我只是将它用于计时器,因为这是您感兴趣的,但您也可以让它“等待”按钮单击(使用 )Subject<Unit>等,然后再继续下一步。

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() {}
}
Run Code Online (Sandbox Code Playgroud)

这是对 Daniel Earwicker 的 AsyncIOPipe 想法的轻微修改:http://smellegantcode.wordpress.com/2008/12/05/asynchronous-sockets-with-yield-return-of-lambdas/