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

StackOverflow https://stackoverflow.com/questions/142003

Вопрос

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

  1. Существует основная форма, в которой размещен некоторый пользовательский элемент управления.
  2. Пользовательский элемент управления выполняет некоторые сложные операции с данными, так что если я напрямую вызываю UserControl_Load метод пользовательский интерфейс перестает отвечать на запросы на время выполнения метода загрузки.
  3. Чтобы преодолеть это, я загружаю данные в другой поток (стараясь изменять существующий код как можно меньше).
  4. Я использовал фоновый рабочий поток, который будет загружать данные и по завершении уведомит приложение о том, что оно выполнило свою работу.
  5. Теперь возникла настоящая проблема.Весь пользовательский интерфейс (основная форма и ее дочерние пользовательские элементы управления) был создан в основном главном потоке.В методе ЗАГРУЗКИ usercontrol я извлекаю данные на основе значений некоторого элемента управления (например, текстового поля) в 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.
    }
}

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

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

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

КОД 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
    }
}

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

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

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

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

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

Это было полезно?

Решение

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

Наверное, я неправильно сформулировал вопрос.

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

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

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

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;
        }
    }
}

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

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

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

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

  • Существует только один поток (UI thread), доступ к которому разрешен Система.Windows.Формы.Контроль и члены его подклассов.
  • Попытка получить доступ к члену Система.Windows.Формы.Контроль из другого потока, отличного от потока пользовательского интерфейса, это вызовет исключение между потоками.
  • Поскольку существует только один поток, все операции пользовательского интерфейса помещаются в очередь в качестве рабочих элементов в этот поток:

enter image description here

enter image description here

BeginInvoke и методы вызова

enter image description here

Призывать

enter image description here

BeginInvoke начать вызывать

enter image description here

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

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

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

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

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

fsw.SynchronizingObject = this

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

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

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

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

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

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

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

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");

Больше никакой возни - все просто.

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

Новый взгляд с использованием 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;
    }
}

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

Добавление Try Catch, автоматического протоколирования исключений и обратного вызова

    /// <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
        }
    }

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

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

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;
        }
    }
}

Я обнаружил необходимость в этом при программировании контроллера приложения monotouch для iOS-телефонов в проекте прототипа Visual Studio winforms за пределами xamarin stuidio.Предпочитая программировать в VS, а не в xamarin studio, насколько это возможно, я хотел, чтобы контроллер был полностью отделен от платформы phone.Таким образом, реализовать это для других фреймворков, таких как 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");
    }
}

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

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);
            }
        }
    }
}

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

CheckForIllegalCrossThreadCalls = false

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

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

(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

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

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

/// <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;
}
this.Invoke(new MethodInvoker(delegate
            {
                //your code here;
            }));

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

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

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

Два способа:

  1. Верните значение в e.result и используйте его для установки значения текстового поля в событии backgroundworker_runworker completed

  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";
    }
}

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

label1.Вызвать(y=()=>label1.Text="текст");

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

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

Существует два варианта выполнения операций с перекрестными потоками.

Control.InvokeRequired Property 

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

SynchronizationContext Post Method

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

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

Перекрестное обновление пользовательского интерфейса с помощью SynchronizationContext | .Net

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top