Метод BeginInvoke (..) не блокирует вызов, а просто помещает новое сообщение в очередь для обработки потоком диспетчера. В размещенном вами коде у вас нет гарантии, что изменение цвета фона будет обработано до запуска DoWorks.
Вы можете назначить более низкий приоритет вызову DoWork, чтобы убедиться, что фон изменяется первым:
private void Button_Click(object sender, RoutedEventArgs e)
{
grid.Background = new SolidColorBrush(Colors.Red);
Dispatcher.BeginInvoke((Action)DoWork, DispatcherPriority.ContextIdle);
}
private void DoWork()
{
Thread.Sleep(1000);
btn.Content = "Done";
}
Но это не лучшее решение, известное человечеству.
Лучше было бы перейти к DoWork в отдельном потоке.
Приведенный выше пример с использованием потокового подхода будет выглядеть так:
private void Button_Click(object sender, RoutedEventArgs e)
{
grid.Background = new SolidColorBrush(Colors.Red);
Task t = new Task(DoWork);
t.Start();
}
private void DoWork()
{
Thread.Sleep(1000);
// Synchronize calls to the UI elements
Application.Current.Dispatcher.BeginInvoke((Action)(() => { btn.Content = "Done"; }));
}
Только не забудьте опубликовать все операции над элементами пользовательского интерфейса в потоке пользовательского интерфейса.