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

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

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

Но я задаюсь вопросом, существует ли более легкий способ, которым я отсутствовал. Какие-либо предложения для более короткой, более простой или более легкой техники? Предположите, что.NET 4 доступна.

14
задан Scott Bussinger 2 April 2010 в 05:40
поделиться

2 ответа

Я думаю, что вы Неплохой Скотт.

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

Вместо этого я бы посоветовал вам включить алгоритм в служебный метод и избегать использования 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 или WinForms Timer класс. Я не уверен, каковы будут компромиссы этих различных таймеров. Я предполагаю, что таймеры DispatcherTimer и WinForm действительно будут работать в приложениях противоположного типа.

РЕДАКТИРОВАТЬ:

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

В 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");
22
ответ дан 1 December 2019 в 12:13
поделиться

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

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 гарантирует, что делегат будет выполнен в потоке пользовательского интерфейса (где существует главный дескриптор родительского окна)

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

2
ответ дан 1 December 2019 в 12:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: