Ситуация следующая: имеется окно с кнопкой button1 и меткой label1. по кнопке запускается какая-то долгая операция, в отдельном потоке. по завершению операции нужно вывести результат label1. При попытке поменять значение label1.Text код падает с исключением InvalidOperationException: WinForms: Cross-thread operation not valid: Control 'label1' accessed from a thread other than the thread it was created on. Недопустимая операция в нескольких потоках: попытка доступа к элементу управления 'label1' не из того потока, в котором он был создан. WPF: The calling thread cannot access this object because a different thread owns it. Вызывающий поток не может получить доступ к данному объекту, так как владельцем этого объекта является другой поток. Пример кода: private void button1_Click(object sender, EventArgs e) { (new Thread((s) => { var result = Worker.SomeLongOperation() // следующая строчка падает c InvalidOperationException: this.label1.Text = result })).Start() } class Worker { public static string SomeLongOperation() { Thread.Sleep(1000) return "результат" } }
Ответ Решение для .NET 4.0 и более поздних версий Использовать Асинхронную модель на основе задач (TAP) и ключевые слова async-await: private async void button1_Click(object sender, EventArgs e) { string result = await Task.Run(() => Worker.SomeLongOperation()) this.label1.Text = result } Преимущества: Код значительно короче других вариантов, вызовы записаны в той последовательности, в которой они выполняются. Никаких коллбеков и ручной работы с потоками. async не дает обработчику события завершиться, но при этом не блокирует UI. Встроенная поддержка ключевых слова async/await появились в .NET 4.5 и Visual Studio 2013. Данное решение также может быть использовано для .NET 4.0 и Silverlight 5, если используется версия Visual Studio не ниже 2012. Для этого нужно установить пакет Microsoft.Bcl.Async из NuGet. Решение с отображением прогресса выполнения Если в процессе выполнения нужно отображать прогресс или промежуточные результаты из второго потока, то можно использовать класс Progress: private async void button1_Click(object sender, EventArgs e) { var progress = new Progress(s => label1.Text = s) string result = await Task.Run(() => Worker.SomeLongOperation(progress)) this.label1.Text = result } class Worker { public static string SomeLongOperation(IProgress progress) { // Perform a long running work... for (var i = 0 i < 10 i++) { Task.Delay(500).Wait() progress.Report(i.ToString()) } return "результат" } } Progress захватывает SynchronizationContext в момент создания, и использует его для выполнения операций, избавляя от ручных вызовов Invoke. Решение для .NET 3.5 и более ранних версий Использовать Invoke/BeginInvoke: // WinForms: this.label1.BeginInvoke((MethodInvoker)(() => this.label1.Text = result)) // WPF: Dispatcher.BeginInvoke((Action)(() => this.label1.Content = result)) Для .NET 2.0, в котором еще не было лямбд, эквивалентный код записывается с помощью анонимных делегатов: // WinForms: this.label1.BeginInvoke((MethodInvoker)(delegate { this.label1.Text = result }) // WPF: Dispatcher.BeginInvoke((Action)(delegate { this.label1.Content = result }) Полный код: private void button1_Click(object sender, EventArgs e) { (new Thread((s) => { var result = Worker.SomeLongOperation() this.label1.BeginInvoke((MethodInvoker)(() => this.label1.Text = result)) })).Start() } BeginInvoke поставит код на выполнение в тот поток, в котором был создан label1 и продолжит выполнение фонового потока. При использовании Invoke вместо BeginInvoke фоновый поток будет приостановлен до завершения выполнения кода в UI потоке.