Недопустимая операция между потоками: доступ к элементу управления из потока, отличного от потока, в котором он был создан - PullRequest
536 голосов
/ 27 сентября 2008

У меня есть сценарий. (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.
    }
}

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

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

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

КОД 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, если условие. Задача загрузки снова выполняется родительским потоком, а не третьим, который я породил.

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

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

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

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

Ответы [ 21 ]

7 голосов
/ 27 сентября 2008

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

6 голосов
/ 11 сентября 2013

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

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

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);
            }
        }
    }
}
6 голосов
/ 10 мая 2017

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

CheckForIllegalCrossThreadCalls = false

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

5 голосов
/ 25 мая 2012

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

(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

4 голосов
/ 22 октября 2014

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

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

/// <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;
}
4 голосов
/ 03 ноября 2017
this.Invoke(new MethodInvoker(delegate
            {
                //your code here;
            }));
3 голосов
/ 05 февраля 2014

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

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
2 голосов
/ 25 марта 2017

Тот же вопрос: как обновлять графический интерфейс пользователя из другой нити в 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";
    }
}
0 голосов
/ 28 апреля 2019

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

this.Invoke((MethodInvoker)delegate
            {
                YourControl.Property= value; // runs thread safe
            });
0 голосов
/ 03 апреля 2018

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

Control.InvokeRequired Property 

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

SynchronizationContext Post Method

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

Интерфейс обновления кросс-нити | .Net

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...