Межпоточная операция недействительна: управление доступно из потока, отличного от потока, в котором он был создан

У меня есть сценарий. (Windows Forms, C#, .NET)

  1. Есть основная форма, в которой размещены некоторые пользовательские элементы управления.
  2. Пользовательский элемент управления выполняет некоторые тяжелые операции с данными, например, если я напрямую вызываю метод UserControl_Load, пользовательский интерфейс перестает отвечать на запросы на время выполнения метода загрузки.
  3. Чтобы преодолеть это, я загружаю данные в другой поток (пытаясь как можно меньше изменить существующий код)
  4. Я использовал фоновый рабочий поток, который будет загружать данные и по завершении уведомит приложение о том, что оно выполнило свою работу.
  5. Теперь возникла настоящая проблема. Весь пользовательский интерфейс (основная форма и ее дочерние элементы управления) был создан в основном основном потоке. В методе LOAD пользовательского элемента управления я получаю данные на основе значений некоторого элемента управления (например, текстового поля) в userControl.

Псевдокод будет выглядеть так:

КОД 1

UserContrl1_LoadDataMethod()
{
    if (textbox1.text == "MyName") // This gives exception
    {
        //Load data corresponding to "MyName".
        //Populate a globale variable List<string> which will be binded to grid at some later stage.
    }
}

Исключение, которое он дал, было

Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on.

Чтобы узнать больше об этом, я немного погуглил, и появилось предложение, например, использовать следующий код

КОД 2

UserContrl1_LoadDataMethod()
{
    if (InvokeRequired) // Line #1
    {
        this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
        return;
    }

    if (textbox1.text == "MyName") // Now it wont give an exception
    {
    //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be binded to grid at some later stage
    }
}

НО НО ... кажется, я вернулся на круги своя. Приложение снова перестать отвечать. Похоже, это связано с выполнением строки # 1 if condition. Задача загрузки снова выполняется родительским потоком, а не третьим, созданным мной.

Не знаю, правильно я это понял или неправильно. Я новичок в потоках.

Как мне решить эту проблему, а также каков эффект выполнения блока Line # 1 if?

Ситуация такая: я хочу загрузить данные в глобальную переменную на основе значения элемента управления. Я не хочу изменять значение элемента управления дочернего потока. Я не собираюсь делать это из дочернего потока.

Таким образом, доступ только к значению, чтобы соответствующие данные могли быть извлечены из базы данных.

Для моего конкретного случая этой ошибки я обнаружил, что обходной путь заключается в использовании BackgroundWorker в форме для обработки частей кода, требующих большого объема данных. (т.е. поместите весь проблемный код в метод backgroundWorker1_DoWork () и вызовите его через backgroundWorker1.RunWorkerAsync ()) ... Эти два источника указали мне правильное направление: stackoverflow.com/questions/4806742/…youtube.com/watch?v=MLrrbG6V1zM

Giollia 18.09.2019 22:12
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
614
1
427 933
22
Перейти к ответу Данный вопрос помечен как решенный

Ответы 22

Элементы управления в .NET обычно не являются потокобезопасными. Это означает, что вам не следует обращаться к элементу управления из потока, отличного от того, в котором он находится. Чтобы обойти это, вам нужно вызывать элемента управления, что и пытается сделать ваш второй образец.

Однако в вашем случае все, что вы сделали, - это передали длительный метод обратно в основной поток. Конечно, это не совсем то, что вы хотите делать. Вам нужно немного переосмыслить это, чтобы все, что вы делаете в основном потоке, - это установка быстрого свойства здесь и там.

Вы хотите использовать Invoke или BeginInvoke только для минимума работы, необходимой для изменения пользовательского интерфейса. Ваш «тяжелый» метод должен выполняться в другом потоке (например, через BackgroundWorker), но затем использовать Control.Invoke / Control.BeginInvoke только для обновления пользовательского интерфейса. Таким образом, ваш поток пользовательского интерфейса будет свободен для обработки событий пользовательского интерфейса и т. д.

См. Мой разделить статью для Пример WinForms - хотя статья была написана до того, как на сцене появился BackgroundWorker, и, боюсь, я не обновлял ее в этом отношении. BackgroundWorker просто немного упрощает обратный вызов.

вот в этом моем состоянии. я даже не меняю UI. Я просто получаю доступ к его текущим значениям из дочернего потока. любые предложения по реализации

Prerak K 27.09.2008 01:26

Вам по-прежнему необходимо перейти к потоку пользовательского интерфейса даже просто для доступа к свойствам. Если ваш метод не может продолжаться до тех пор, пока значение не будет получено, вы можете использовать делегат, который возвращает значение. Но да, пройдите через поток пользовательского интерфейса.

Jon Skeet 27.09.2008 01:38

Привет, Джон, я считаю, что ты ведешь меня в правильном направлении. Да, мне нужно значение без него, я не могу продолжить. Не могли бы вы подробнее рассказать о том, что «Использование делегата, возвращающего значение». Спасибо

Prerak K 27.09.2008 01:46

Используйте делегат, например Func <string>: string text = textbox1.Invoke ((Func <string>) () => textbox1.Text); (Предполагая, что вы используете C# 3.0 - в противном случае вы могли бы использовать анонимный метод.)

Jon Skeet 27.09.2008 01:49
Ответ принят как подходящий

Согласно Комментарий к обновлению Прерака К. (с момента удаления):

I guess I have not presented the question properly.

Situation is this: I want to load data into a global variable based on the value of a control. I don't want to change the value of a control from the child thread. I'm not going to do it ever from a child thread.

So only accessing the value so that corresponding data can be fetched from the database.

Тогда желаемое решение должно выглядеть так:

UserContrl1_LOadDataMethod()
{
    string name = "";
    if (textbox1.InvokeRequired)
    {
        textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
    }
    if (name == "MyName")
    {
        // do whatever
    }
}

Выполните серьезную обработку в отдельном потоке до, вы пытаетесь переключиться обратно в поток управления. Например:

UserContrl1_LOadDataMethod()
{
    if (textbox1.text= = "MyName") //<<======Now it wont give exception**
    {
        //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be
        //bound to grid at some later stage
        if (InvokeRequired)
        {
            // after we've done all the processing, 
            this.Invoke(new MethodInvoker(delegate {
                // load the control with the appropriate data
            }));
            return;
        }
    }
}

Я давно не занимался программированием на C#, но, судя по статье MSDN и моим разрозненным знаниям, похоже, что это так.

Jeff Hubbard 14.12.2016 00:17

Разница в том, что BeginInvoke () работает асинхронно, а Invoke () - синхронно. stackoverflow.com/questions/229554/…

frzsombor 13.10.2017 03:18

Вам нужно посмотреть на пример Backgroundworker:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx Особенно то, как он взаимодействует со слоем пользовательского интерфейса. Судя по вашему сообщению, это, кажется, ответ на ваши вопросы.

У меня была эта проблема с FileSystemWatcher, и я обнаружил, что следующий код решил проблему:

fsw.SynchronizingObject = this

Затем элемент управления использует текущий объект формы для обработки событий и, следовательно, будет находиться в том же потоке.

Это спасло мой бекон. В VB.NET я использовал .SynchronizingObject = Me

codingcoding 23.06.2016 05:40

Самым чистым (и правильным) решением проблем с перекрестным потоком пользовательского интерфейса является использование SynchronizationContext, см. Статью Синхронизация вызовов пользовательского интерфейса в многопоточном приложении, в ней это очень хорошо объясняется.

Вот альтернативный способ, если у объекта, с которым вы работаете, нет

(InvokeRequired)

Это полезно, если вы работаете с основной формой в классе, отличном от основной формы, с объектом, который находится в основной форме, но не имеет InvokeRequired.

delegate void updateMainFormObject(FormObjectType objectWithoutInvoke, string text);

private void updateFormObjectType(FormObjectType objectWithoutInvoke, string text)
{
    MainForm.Invoke(new updateMainFormObject(UpdateObject), objectWithoutInvoke, text);
}

public void UpdateObject(ToolStripStatusLabel objectWithoutInvoke, string text)
{
    objectWithoutInvoke.Text = text;
}

Он работает так же, как и выше, но это другой подход, если у вас нет объекта с invokerequired, но у вас есть доступ к MainForm

Я обнаружил необходимость в этом при программировании контроллера монотач-приложения для iOS-Phone в проекте прототипа Visual Studio winforms за пределами xamarin stuidio. Предпочитая программировать в VS, а не в xamarin studio, насколько это возможно, я хотел, чтобы контроллер был полностью отделен от инфраструктуры телефона. Таким образом, реализация этого для других фреймворков, таких как Android и Windows Phone, будет намного проще для использования в будущем.

Мне нужно было решение, в котором графический интерфейс мог бы реагировать на события без необходимости иметь дело с кодом переключения между потоками за каждым нажатием кнопки. По сути, позвольте контроллеру класса обрабатывать это, чтобы клиентский код оставался простым. У вас может быть много событий в графическом интерфейсе, где, как если бы вы могли обрабатывать их в одном месте в классе, было бы чище. Я не многопрофильный эксперт, дайте мне знать, если это что-то не так.

public partial class Form1 : Form
{
    private ExampleController.MyController controller;

    public Form1()
    {          
        InitializeComponent();
        controller = new ExampleController.MyController((ISynchronizeInvoke) this);
        controller.Finished += controller_Finished;
    }

    void controller_Finished(string returnValue)
    {
        label1.Text = returnValue; 
    }

    private void button1_Click(object sender, EventArgs e)
    {
        controller.SubmitTask("Do It");
    }
}

Форма графического интерфейса пользователя не знает, что контроллер выполняет асинхронные задачи.

public delegate void FinishedTasksHandler(string returnValue);

public class MyController
{
    private ISynchronizeInvoke _syn; 
    public MyController(ISynchronizeInvoke syn) {  _syn = syn; } 
    public event FinishedTasksHandler Finished; 

    public void SubmitTask(string someValue)
    {
        System.Threading.ThreadPool.QueueUserWorkItem(state => submitTask(someValue));
    }

    private void submitTask(string someValue)
    {
        someValue = someValue + " " + DateTime.Now.ToString();
        System.Threading.Thread.Sleep(5000);
//Finished(someValue); This causes cross threading error if called like this.

        if (Finished != null)
        {
            if (_syn.InvokeRequired)
            {
                _syn.Invoke(Finished, new object[] { someValue });
            }
            else
            {
                Finished(someValue);
            }
        }
    }
}

Модель потоков в пользовательском интерфейсе

Пожалуйста, прочтите Модель потоковой передачи в приложениях пользовательского интерфейса (старая ссылка на VB здесь), чтобы понять основные концепции. Ссылка ведет на страницу, описывающую потоковую модель WPF. Однако в Windows Forms используется та же идея.

Поток пользовательского интерфейса

  • Есть только один поток (поток пользовательского интерфейса), которому разрешен доступ к System.Windows.Forms.Control и членам его подклассов.
  • Попытка получить доступ к члену System.Windows.Forms.Control из потока, отличного от потока пользовательского интерфейса, вызовет исключение между потоками.
  • Поскольку существует только один поток, все операции пользовательского интерфейса помещаются в очередь как рабочие элементы в этом потоке:

  • Если для потока пользовательского интерфейса нет работы, тогда есть пробелы в бездействии, которые могут использоваться вычислениями, не связанными с пользовательским интерфейсом.
  • Чтобы использовать указанные пробелы, используйте методы System.Windows.Forms.Control.Invoke или System.Windows.Forms.Control.BeginInvoke:

Методы BeginInvoke и Invoke

  • Затраты на вычисления вызываемого метода должны быть небольшими, как и накладные расходы на вычисления методов обработчика событий, потому что там используется поток пользовательского интерфейса - то же самое, что отвечает за обработку пользовательского ввода. Независимо от того, System.Windows.Forms.Control.Invoke или System.Windows.Forms.Control.BeginInvoke.
  • Для выполнения дорогостоящей вычислительной операции всегда используйте отдельный поток. Начиная с .NET 2.0, Справочная информация предназначен для выполнения дорогостоящих вычислительных операций в Windows Forms. Однако в новых решениях вы должны использовать шаблон async-await, как описано здесь.
  • Используйте методы System.Windows.Forms.Control.Invoke или System.Windows.Forms.Control.BeginInvoke только для обновления пользовательского интерфейса. Если вы используете их для тяжелых вычислений, ваше приложение заблокирует:

Вызвать

BeginInvoke

Кодовое решение

Прочтите ответы на вопрос Как обновить графический интерфейс из другого потока на C#?. Для C# 5.0 и .NET 4.5 рекомендуемое решение - здесь.

Например, чтобы получить текст из элемента управления потоком пользовательского интерфейса:

Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String

Private Function GetControlText(ByVal ctl As Control) As String
    Dim text As String

    If ctl.InvokeRequired Then
        text = CStr(ctl.Invoke(
            New GetControlTextInvoker(AddressOf GetControlText), ctl))
    Else
        text = ctl.Text
    End If

    Return text
End Function

В том же духе, что и в предыдущих ответах, но очень короткое дополнение, которое позволяет использовать все свойства элемента управления без исключения вызова между потоками.

Вспомогательный метод

/// <summary>
/// Helper method to determin if invoke required, if so will rerun method on correct thread.
/// if not do nothing.
/// </summary>
/// <param name = "c">Control that might require invoking</param>
/// <param name = "a">action to preform on control thread if so.</param>
/// <returns>true if invoke required</returns>
public bool ControlInvokeRequired(Control c, Action a)
{
    if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate
    {
        a();
    }));
    else return false;

    return true;
}

Пример использования

// usage on textbox
public void UpdateTextBox1(String text)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return;
    textBox1.Text = ellapsed;
}

//Or any control
public void UpdateControl(Color c, String s)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(myControl, () => UpdateControl(c, s))) return;
    myControl.Text = s;
    myControl.BackColor = c;
}

Я считаю, что код проверки и вызова, который должен быть замусорен во всех методах, связанных с формами, слишком подробен и ненужен. Вот простой метод расширения, который позволяет полностью избавиться от него:

public static class Extensions
{
    public static void Invoke<TControlType>(this TControlType control, Action<TControlType> del) 
        where TControlType : Control
        {
            if (control.InvokeRequired)
                control.Invoke(new Action(() => del(control)));
            else
                del(control);
    }
}

И тогда вы можете просто сделать это:

textbox1.Invoke(t => t.Text = "A");

Больше не нужно возиться - просто.

что здесь

Rawat 06.01.2018 20:27

@Rawat t в этом случае будет textbox1 - он передается в качестве аргумента

Rob 07.01.2018 05:52

Следуйте простейшему (на мой взгляд) способу модификации объектов из другого потока:

using System.Threading.Tasks;
using System.Threading;

namespace TESTE
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Action<string> DelegateTeste_ModifyText = THREAD_MOD;
            Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD");
        }

        private void THREAD_MOD(string teste)
        {
            textBox1.Text = teste;
        }
    }
}

Новый вид с использованием Async / Await и обратных вызовов. Вам понадобится всего одна строка кода, если вы сохраните метод расширения в своем проекте.

/// <summary>
/// A new way to use Tasks for Asynchronous calls
/// </summary>
public class Example
{
    /// <summary>
    /// No more delegates, background workers etc. just one line of code as shown below
    /// Note it is dependent on the XTask class shown next.
    /// </summary>
    public async void ExampleMethod()
    {
        //Still on GUI/Original Thread here
        //Do your updates before the next line of code
        await XTask.RunAsync(() =>
        {
            //Running an asynchronous task here
            //Cannot update GUI Thread here, but can do lots of work
        });
        //Can update GUI/Original thread on this line
    }
}

/// <summary>
/// A class containing extension methods for the Task class 
/// Put this file in folder named Extensions
/// Use prefix of X for the class it Extends
/// </summary>
public static class XTask
{
    /// <summary>
    /// RunAsync is an extension method that encapsulates the Task.Run using a callback
    /// </summary>
    /// <param name = "Code">The caller is called back on the new Task (on a different thread)</param>
    /// <returns></returns>
    public async static Task RunAsync(Action Code)
    {
        await Task.Run(() =>
        {
            Code();
        });
        return;
    }
}

Вы можете добавить другие вещи к методу Extension, например, заключить его в оператор Try / Catch, позволяя вызывающей стороне сообщить ему, какой тип возвращать после завершения, обратный вызов исключения для вызывающей стороны:

Добавление Try Catch, Auto Exception Logging и CallBack

    /// <summary>
    /// Run Async
    /// </summary>
    /// <typeparam name = "T">The type to return</typeparam>
    /// <param name = "Code">The callback to the code</param>
    /// <param name = "Error">The handled and logged exception if one occurs</param>
    /// <returns>The type expected as a competed task</returns>

    public async static Task<T> RunAsync<T>(Func<string,T> Code, Action<Exception> Error)
    {
       var done =  await Task<T>.Run(() =>
        {
            T result = default(T);
            try
            {
               result = Code("Code Here");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unhandled Exception: " + ex.Message);
                Console.WriteLine(ex.StackTrace);
                Error(ex);
            }
            return result;

        });
        return done;
    }
    public async void HowToUse()
    {
       //We now inject the type we want the async routine to return!
       var result =  await RunAsync<bool>((code) => {
           //write code here, all exceptions are logged via the wrapped try catch.
           //return what is needed
           return someBoolValue;
       }, 
       error => {

          //exceptions are already handled but are sent back here for further processing
       });
        if (result)
        {
            //we can now process the result because the code above awaited for the completion before
            //moving to this statement
        }
    }

Тот же вопрос: как-обновить-графический-интерфейс-из-другого-потока-в-c

Два пути:

  1. Возвращаемое значение в e.result и его использование для установки значения текстового поля yout в событии backgroundWorker_RunWorkerCompleted.

  2. Объявите некоторую переменную для хранения таких значений в отдельном классе (который будет работать как держатель данных). Создайте статический экземпляр этого класса, и вы можете получить к нему доступ через любой поток.

Пример:

public  class data_holder_for_controls
{
    //it will hold value for your label
    public  string status = string.Empty;
}

class Demo
{
    public static  data_holder_for_controls d1 = new data_holder_for_controls();
    static void Main(string[] args)
    {
        ThreadStart ts = new ThreadStart(perform_logic);
        Thread t1 = new Thread(ts);
        t1.Start();
        t1.Join();
        //your_label.Text=d1.status; --- can access it from any thread 
    }

    public static void perform_logic()
    {
        //put some code here in this function
        for (int i = 0; i < 10; i++)
        {
            //statements here
        }
        //set result in status variable
        d1.status = "Task done";
    }
}

Это не рекомендуемый способ решения этой ошибки, но вы можете быстро ее подавить, и он выполнит свою работу. Я предпочитаю это для прототипов или демонстраций. добавлять

CheckForIllegalCrossThreadCalls = false

в конструкторе Form1().

Я знаю, что уже слишком поздно. Однако даже сегодня, если у вас возникают проблемы с доступом к элементам управления кросс-потоком? Это самый короткий ответ на сегодняшний день: P

Invoke(new Action(() =>
                {
                    label1.Text = "WooHoo!!!";
                }));

Вот как я получаю доступ к любому элементу управления формой из потока.

Это дает мне Invoke or BeginInvoke cannot be called on a control until the window handle has been created. Решил здесь

rupweb 24.01.2018 19:22

this.Invoke(new MethodInvoker(delegate
            {
                //your code here;
            }));

Действие y; // объявлен внутри класса

label1.Invoke (y = () => label1.Text = "текст");

Есть два варианта операций с поперечной резьбой.

Control.InvokeRequired Property 

а второй - использовать

SynchronizationContext Post Method

Control.InvokeRequired полезен только при работе элементов управления, унаследованных от класса Control, в то время как SynchronizationContext можно использовать где угодно. Некоторая полезная информация представлена ​​по следующим ссылкам

Пользовательский интерфейс обновления перекрестных потоков | .Сеть

Пользовательский интерфейс межпоточного обновления с использованием SynchronizationContext | .Сеть

Просто используйте это:

this.Invoke((MethodInvoker)delegate
            {
                YourControl.Property= value; // runs thread safe
            });

Простой и многоразовый способ решения этой проблемы.

Метод расширения

public static class FormExts
{
    public static void LoadOnUI(this Form frm, Action action)
    {
        if (frm.InvokeRequired) frm.Invoke(action);
        else action.Invoke();
    }
}

Пример использования

private void OnAnyEvent(object sender, EventArgs args)
{
    this.LoadOnUI(() =>
    {
        label1.Text = "";
        button1.Text = "";
    });
}

Кто-нибудь может возразить против такого подхода? Это кажется безумно простым по сравнению с популярными ответами.

Programmer Paul 03.10.2020 23:52

Другие вопросы по теме