Контроль потока.
У меня есть функция
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);
...
Наслаждаться...