Отложенная отправка?

В 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 - это задержка, спасибо за любой вход:)

Ответ 1

Я бы не предположил, что DispatcherTimer тяжелый... почему бы просто не написать метод расширения на Dispatcher, который позволяет вам использовать синтаксис, который вы хотите, и использует DispatcherTimer в фоновом режиме? Я бы лично назвал его DelayInvoke, а не BeginInvoke, хотя... и я также исправлю его, чтобы всегда использовать Action, а не произвольный делегат..., что упростит использование лямбда-выражений:

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

(Я склонен считать его более читаемым, если анонимные функции используются в качестве последнего аргумента в вызове метода, но это только личное предпочтение.)

Учитывая, что вы, возможно, захотите использовать миллисекунды большую часть времени, у вас может быть и другой вспомогательный метод:

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

Другой альтернативой попыткам является просто использовать существующий метод BeginInvoke, но с очень низким приоритетом, поэтому ваш делегат будет вызван только после завершения всего остального. Не зная подробностей вашей ситуации, трудно понять, будет ли это работать или нет, но стоит попробовать.

Ответ 2

Способ .NET 4.5:

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

Ответ 3

Возможно, вы можете использовать класс Paul Stowell DelayBinding: http://www.paulstovell.com/wpf-delaybinding.

С помощью этого can может привязываться к DependencyProperty для класса, который может выполнить действие при изменении свойства. Связывание будет управлять задержкой. Может показаться особенно приятным, если у вас есть конструкция типа MVVM.

Ответ 4

Я не люблю 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);