У меня была ситуация, подошедшая, который потребовал выполнения лямбда-выражения на потоке UI после задержки. Я думал о нескольких способах сделать это и наконец обоснованный на этом подходе
Task.Factory.StartNew(() => Thread.Sleep(1000))
.ContinueWith((t) => textBlock.Text="Done",TaskScheduler.FromCurrentSynchronizationContext());
Но я задаюсь вопросом, существует ли более легкий способ, которым я отсутствовал. Какие-либо предложения для более короткой, более простой или более легкой техники? Предположите, что.NET 4 доступна.
Я думаю, что вы Неплохой Скотт.
Единственная небольшая проблема, с которой, как мне кажется, некоторые могут столкнуться, заключается в том, что вы блокируете поток, чтобы выполнить свою задержку. Конечно, это фоновый поток, и вряд ли он вызовет проблемы, если вы не выполните множество этих вызовов одновременно (каждый из которых связывает поток), но он все равно, вероятно, неоптимален.
Вместо этого я бы посоветовал вам включить алгоритм в служебный метод и избегать использования 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");
Я думаю, что самый простой способ - использовать 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 гарантирует, что делегат будет выполнен в потоке пользовательского интерфейса (где существует главный дескриптор родительского окна)
Возможно, существует лучший вариант.