Контроль потока.

У меня есть функция

public void ShowAllFly()
{  
        cbFly.Items.Clear();
        cbFly.Items.Add("Uçuş Seçiniz...");

        dsFlyTableAdapters.tblFlyTableAdapter _t=new KTHY.dsFlyTableAdapters.tblFlyTableAdapter();
        dsFly _mds = new dsFly();
        _mds.EnforceConstraints = false;
        dsFly.tblFlyDataTable _m = _mds.tblFly;
        _t.Fill(_m);
        foreach (DataRow _row in _m.Rows)
        {
            cbFly.Items.Add(_row["FlyID"].ToString()+"-"+_row["FlyName"].ToString() + "-" + _row["FlyDirection"].ToString() + "-" + _row["FlyDateTime"].ToString());
        }
        _Thread.Abort();
        timer1.Enabled = false;
        WaitPanel.Visible = false;
}

В функции Form_Load.

{
    _Thread = new System.Threading.Thread(new System.Threading.ThreadStart(ShowAllFly));
    _Thread.Start();
    _Thread.Priority = System.Threading.ThreadPriority.Normal;
}

Но когда я запустил его,

в функции ShowAllFly

cbFly.Items.Clear(); ----  HERE Gives ERROR  LIKE  Control.Invoke must be used to interact with controls created on a separate thread.

В чем проблема?

Ответ 1

В Windows Forms есть два золотых правила потоковой передачи:

  • Не прикасайтесь к каким-либо свойствам или методам управления (отличным от тех, которые явно указаны в порядке) из любого потока, отличного от того, который создал элемент управления "handle" (обычно есть только один поток пользовательского интерфейса).
  • Не блокируйте поток пользовательского интерфейса в течение какого-либо значительного промежутка времени, или вы не будете реагировать на запросы

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

Лямбда-выражения в С# 3 (или анонимные методы на С# 2) делают это намного приятнее.

Например, вы можете использовать:

cbFly.Invoke((MethodInvoker)(() => cbFly.Items.Clear()));

Все скобки немного мешают, поэтому вы можете добавить такой метод расширения, как если бы вы использовали С# 3:

public static void Invoke(this Control control, MethodInvoker action)
{
    control.Invoke(action);
}

Тогда вы могли бы сделать:

cbFly.Invoke(() => cbFly.Items.Clear());

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

Смотрите мой учебник по потокам или Joe Albahari для более подробности.

В качестве второстепенного вопроса, я вижу, что вы используете Thread.Abort - на самом деле в своем собственном потоке, несмотря на то, что после него есть другие вызовы. Зачем? Прерывание любого потока, отличного от вашего собственного, - это вызов типа "только для чрезвычайных ситуаций" (который, как правило, следует за тем, чтобы приложение было выгружено в любом случае), и я не вижу причин прекратить текущий поток, когда все еще будет работать после этого...

Ответ 2

Взаимодействие с элементами управления в другом (ui) потоке необходимо вызвать так:

public delegate void ProcessResultDelegate(string result);
void ProcessResult(string result)
{
    if (textBox1.InvokeRequired)
    {
        var d = new ProcessResultDelegate(ProcessResult);
        d.Invoke(result);
    }
    else
    {
        textBox1.Text = result;
    }
}

Ответ 3

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

В вашем примере вы пытаетесь изменить различные элементы управления из потока, который не создал элемент управления. Чтобы обойти эту проблему с учетом вашего примера, сделайте это вместо этого (предполагая, что метод ShowAllFly() является методом в вашей форме):

public void ShowAllFly()
{
    Invoke((MethodsInvoker) delegate {
        cbFly.Items.Clear();
        cbFly.Items.Add("Uçuş Seçiniz...");
        dsFlyTableAdapters.tblFlyTableAdapter _t =
            new KTHY.dsFlyTableAdapters.tblFlyTableAdapter();
        dsFly _mds = new dsFly();
        _mds.EnforceConstraints = false;
        dsFly.tblFlyDataTable _m = _mds.tblFly;
        _t.Fill(_m);
        foreach (DataRow _row in _m.Rows)
        {
            cbFly.Items.Add(_row["FlyID"].ToString() + "-" +
                            _row["FlyName"].ToString() + "-" +
                            _row["FlyDirection"].ToString() + "-" +
                            _row["FlyDateTime"].ToString());
        }
        //_Thread.Abort(); // WHY ARE YOU TRYING TO DO THIS?
        timer1.Enabled = false;
        WaitPanel.Visible = false;
    } );
}

Чтобы подчеркнуть точку @Jon Skeet, я прокомментировал вызов, чтобы прервать поток. Поток закончится сам по себе. Там нет причин прервать его таким образом.

Ответ 4

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

Thread thread = new Thread(new delegate_method(method));//you must create delegate before
thread.start ();
Thread thread2 = new Thread(new delegate_method(method2));//you must create delegate before
thread.start ();

обрабатывать два процесса одновременно

    void method ()
{
//do something here -- working background Remember can not control any UI control from here
finish_thread()
}

void method2 ()
{
//do something here -- working background Remember can not control any UI control from here
finish_thread()
}

void finish_thread()
{
if(invoke.Required)
{
//Here you have to call delegate method here with UI
BeginInvoke(new delegate_method(finish_thread));
}
else
{
//Now you can control UI thread from here and also you finished background work
//Do something working with UI thread
textBox.Text = "";
}
}

Ответ 5

Это лучший способ работы с элементами управления в потоке.

Сначала вы должны использовать нить из одной нити.

...
Thread th = new Thread(yourThreadStart);
            th.SetApartmentState(ApartmentState.STA);
th.Start();
...

Затем скопируйте этот метод между вашим кодом!

public static void SetControlThreadSafe(Control control, Action<object[]> action, object[] args)
{
      if (control.InvokeRequired)
            try { control.Invoke(new Action<Control, Action<object[]>, object[]>(SetControlThreadSafe), control, action, args); } catch { }
      else action(args);
}

Наконец, ваши изменения управления должны быть выполнены, как показано ниже:

...

    SetControlThreadSafe(textbox1, (arg) =>
          {
                textbox1.Text = "I'm Working in a Thread";
          }, null);
...

Наслаждаться...