Страницы

Поиск по вопросам

воскресенье, 24 ноября 2019 г.

Работа с контролами из фонового потока


Ситуация следующая: 


имеется окно с кнопкой 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 "результат";
    }
}

    


Ответы

Ответ 1



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

Ответ 2



Еще есть пример для UI для безопасного вызова из не главного потока. Чтобы взаимодействовать с элементами интерфейса из другого thread void SomeAsyncMethod() { // Do some work if (this.InvokeRequired) { this.Invoke((MethodInvoker)(() => { DoUpdateUI(); } )); } else { DoUpdateUI(); } } void DoUpdateUI() { // Your UI update code here } Здесь this.Invoke метод контрола

Комментариев нет:

Отправить комментарий