.NET: Лучший способ выполнить lambda в потоке пользовательского интерфейса после задержки?

У меня возникла ситуация, требующая выполнения выражения лямбда в потоке пользовательского интерфейса после задержки. Я подумал о нескольких способах сделать это и, наконец, остановился на этом подходе

Task.Factory.StartNew(() => Thread.Sleep(1000))
    .ContinueWith((t) => textBlock.Text="Done",TaskScheduler.FromCurrentSynchronizationContext());

Но мне интересно, есть ли более простой способ, который я пропустил. Любые предложения для более короткой, простой или простой техники? Предположим, что .NET 4 доступен.

Ответ 1

Я думаю, что у тебя неплохой Скотт.

Единственная небольшая проблема, которая, по моему мнению, может быть связана с ней, заключается в том, что вы блокируете поток, чтобы выполнить задержку. Конечно, это фоновый поток и вряд ли вызовет проблемы, если вы не выполняете много этих вызовов одновременно (каждый привязывает поток), но он все еще, вероятно, субоптимален.

Вместо этого я предлагаю вам включить алгоритм в метод утилиты и избежать использования Thread.Sleep.

Очевидно, что существует множество способов сделать это, но здесь один:

public static class UICallbackTimer
{
    public static void DelayExecution(TimeSpan delay, Action action)
    {
        System.Threading.Timer timer = null;
        SynchronizationContext context = SynchronizationContext.Current;

        timer = new System.Threading.Timer(
            (ignore) =>
            {
                timer.Dispose();

                context.Post(ignore2 => action(), null);
            }, null, delay, TimeSpan.FromMilliseconds(-1));
    }
}

Для использования:

    UICallbackTimer.DelayExecution(TimeSpan.FromSeconds(1),
        () => textBlock.Text="Done");

Конечно, вы также можете написать реализацию этого метода DelayExecution, который использует другие типы таймеров, такие как WPF DispatcherTimer или класс Timer WinForms. Я не знаю, какими будут компромиссы этих разных таймеров. Мое предположение было бы типом DispatcherTimer и WinForm, которые по-прежнему будут работать на приложениях противоположного типа.

EDIT:

Повторяя мой ответ, я думаю, что на самом деле у меня возникло бы желание включить это в метод расширения, который работает в контексте синхронизации - если вы подумаете об этом, более общее утверждение будет заключаться в том, что вы должны иметь возможность отправлять сообщения вернуться к контексту синхронизации после некоторой задержки.

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

public static class SyncContextExtensions
{
    public static void Post(this SynchronizationContext context, TimeSpan delay, Action action)
    {
        System.Threading.Timer timer = null;

        timer = new System.Threading.Timer(
            (ignore) =>
            {
                timer.Dispose();

                context.Post(ignore2 => action(), null);
            }, null, delay, TimeSpan.FromMilliseconds(-1));
    }
}

и используйте:

        SynchronizationContext.Current.Post(TimeSpan.FromSeconds(1),
            () => textBlock.Text="Done");

Ответ 2

Я думаю, что самый простой способ - использовать System.Windows.Forms.Timer, если lambda не является случайной функцией.

this._timer.Interval = 1000;
this._timer.Tick += (s, e) => this.textBlock.Text = "Done";

Если labda не нужно выполнять в цикле, добавьте это;

this.timer1.Tick += (s, e) => this.timer1.Stop();

И вызовите

this.timer1.Start();

где это необходимо.

Другой способ - использовать методы Invoke.

delegate void FooHandler();

private void button1_Click(object sender, EventArgs e)
        {

            FooHandler handle = () =>  Thread.Sleep(1000); 
            handle.BeginInvoke(result => { ((FooHandler)((AsyncResult)result).AsyncDelegate).EndInvoke(result); this.textBox1.Invoke((FooHandler)(() => this.textBox1.Text = "Done")); }, null);
        }

Control.Invoke гарантирует, что делегат будет выполнен в потоке пользовательского интерфейса (где существует главный дескриптор родительского окна)

Возможно, существует лучший вариант.