延迟调度调用?

19

在WPF中,由于界面更新的复杂性,有时我需要在短暂的延迟后执行操作。

目前,我只是这样做:

        var dt = new DispatcherTimer(DispatcherPriority.Send);
        dt.Tick += (s, e) =>
        {
            dt.Stop();
            //DoStuff
        };
        dt.Interval = TimeSpan.FromMilliseconds(200);
        dt.Start();

但是每次创建一个新的计时器可能有些丑陋,而且可能会造成过多开销。从性能角度来看,最好的解决方案是什么?并且重写上述代码的好方法是什么,比如:

        this.Dispatcher.BeginInvoke(new Action(delegate()
        {
            //DoStuff
        }), DispatcherPriority.Send,TimeSpan.FromMilliseconds(200));

当Timespan表示延迟时间时,谢谢您的任何意见 :)


不必等待某些东西再进行更新,难道不能将更新 UI 的责任移交给正在等待的那个任务吗?这样可以避免使用计时器。 - x0n
1
很遗憾,这些都是一些hacky的东西,比如更新UI并在短暂延迟后隐藏它,以便在再次显示时更新窗口。 - Homde
1
让我感到更好的是,很多其他人都有和我一样的问题和同样的hack解决方案。 - rollsch
4个回答

14

我不会假设DispatcherTimer是重量级的...为什么不直接在Dispatcher上编写一个扩展方法,允许您使用所需的语法,并在后台使用DispatcherTimer?我个人会称其为DelayInvoke而不是BeginInvoke...并且还会将其修复为始终使用Action而不是任意委托...这样将更容易使用lambda表达式:

Dispatcher.DelayInvoke(TimeSpan.FromMilliseconds(200), () => { ... 
});

我发现如果在方法调用的最后一个参数使用匿名函数,代码会更易读,但这只是我的个人偏好。

考虑到大多数情况下您可能想要使用毫秒,您还可以编写另一个辅助方法:

Dispatcher.DelayInvokeMillis(200, () => { ... 
});

尝试的另一种选择是仅使用现有的BeginInvoke方法,但优先级非常低,这样您的委托将在所有其他操作完成后才被调用。如果不知道您的具体情况细节,很难确定是否会起作用 - 但值得一试。


一个扩展方法会很好,但这是否意味着我必须先在调度程序上执行begininvoke以便在正确的线程上运行,然后设置计时器,这比仅仅拥有计时器多了一些开销?虽然这可能不是问题... - Homde
@MattiasK:为什么需要这样做呢?它实际上并不需要与调度程序进行交互...那只是为了保持一致性。诚然,我不知道如果您从不同的线程创建DispatcherTimer会发生什么...但如果直接完成时可以工作,那么从扩展方法完成也不会有任何额外的问题。 - Jon Skeet
3
我已经检查过了,你可以告诉DispatcherTimer构造函数应该在哪个Dispatcher上运行 - 因此你会使用扩展方法的那个重载,并指定传递给方法的Dispatcher。 - Jon Skeet

13

一个 .NET 4.5 的方法:

    public async void MyMethod()
    {
        await Task.Delay(20); 
        await Dispatcher.BeginInvoke((Action)DoStuff);
    }

上面的计时器方法有什么优缺点? - rollsch
更简单,因为它不需要 lambda...大概就是这样。 - Anthony Hayward

3

我不喜欢DispatcherTimer,因为除了通过函数作用域/闭包传递参数之外,没有其他简单的方法。

相反,我使用Task.Delay()并将其包装成一个Dispatcher.Delay()扩展方法:

public static class DispatcherExtensions
{

    public static void Delay(this Dispatcher disp, int delayMs,
                             Action<object> action, object parm = null)
    {
        var ignore = Task.Delay(delayMs).ContinueWith((t) =>
        {
            disp.Invoke(action, parm);
        });
    }

    public static void DelayWithPriority(this Dispatcher disp, int delayMs,
                      Action<object> action, object parm = null, 
                      DispatcherPriority priority = DispatcherPriority.ApplicationIdle)
    {
        var ignore = Task.Delay(delayMs).ContinueWith((t) =>
        {
            disp.BeginInvoke(action, priority, parm);
        });

    }

    public static async Task DelayAsync(this Dispatcher disp, int delayMs,
                      Action<object> action, object parm = null,
                      DispatcherPriority priority = DispatcherPriority.ApplicationIdle)
    {
        await Task.Delay(delayMs);
        await disp.BeginInvoke(action, priority, parm);
    }
}

那么要调用它:

Dispatcher.Delay(4000, (win) =>
{
     var window = win as MainWindow;
     window.ShowStatus(null, 0);
},this);

非常整洁的解决方案。您能详细说明使用Task.Delay与Timer的优缺点吗?您是否只关心传递参数的能力? - rollsch

0
你可以使用Paul Stowell的DelayBinding类,从这里获取:http://www.paulstovell.com/wpf-delaybinding
使用它可以将绑定到一个类的DependencyProperty上,当属性更改时可以执行操作。绑定将管理延迟。 如果你有MVVM类型的设计,这可能特别好用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接