.NET: Лучший способ выполнить лямбду в потоке пользовательского интерфейса после задержки? - PullRequest
14 голосов
/ 02 апреля 2010

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

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

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

Ответы [ 2 ]

22 голосов
/ 24 мая 2010

Я думаю, у тебя довольно хороший Скотт.

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

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

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 голосов
/ 02 апреля 2010

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

Может быть, существует лучший вариант.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...