Ситуация следующая:
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 "результат";
}
}
Использовать Асинхронную модель на основе задач (TAP) и ключевые слова async-await:
private async void button1_Click(object sender, EventArgs e)
{
string result = await Task.Factory.StartNew<string>(
() => Worker.SomeLongOperation(),
TaskCreationOptions.LongRunning);
this.label1.Text = result;
}
Преимущества:
Встроенная поддержка ключевых слова 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<string>(s => label1.Text = s);
string result = await Task.Factory.StartNew<string>(
() => Worker.SomeLongOperation(progress),
TaskCreationOptions.LongRunning);
this.label1.Text = result;
}
class Worker
{
public static string SomeLongOperation(IProgress<string> 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.
Использовать 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 потоке.
Апостиль в Лос-Анджелесе без лишних нервов и бумажной волокиты
Основные этапы разработки сайта для стоматологической клиники
Продвижение своими сайтами как стратегия роста и независимости