线程控制.Invoke

10
我有一个函数。
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.

问题是什么?

5个回答

54

在Windows Forms中,有两个线程的黄金规则:

  • 不要从任何线程(除了创建控件“handle”的线程(通常只有一个UI线程)之外的线程)触碰控件属性或方法(除了明确列出为可以使用的那些)
  • 不要让UI线程阻塞太长时间,否则会使应用程序无响应。

要从不同的线程与UI交互,需要通过委托调用Control.Invoke/BeginInvoke将调用"marshall"到UI线程。您可以使用InvokeRequired属性测试是否需要调用Invoke,但这些天我个人倾向于随时都这样做-当您不需要调用时,没有太多惩罚。

C#3中的Lambda表达式(或C#2中的匿名方法)也使这变得更加愉快。

例如,您可以使用以下内容:

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

所有这些括号可能会有点碍事,因此如果你正在使用C# 3,你可能想要添加一个扩展方法,就像这样:

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

那么你可以这样做:

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

这个做法要简单得多。通常情况下,您可以通过在委托内捕获需要访问的任何变量来使用MethodInvoker

有关更多详细信息,请参见我的线程教程Joe Albahari的教程

其次,我看到您正在使用Thread.Abort - 实际上是在您自己的线程上调用,尽管后面还有其他调用。为什么?除了紧急情况之外,终止任何线程(除了您自己的线程)都应该被视为“仅限紧急情况”的调用(通常应该紧跟着应用程序被卸载),我无法看到在仍有工作要做的情况下终止当前线程的任何理由...


7

在另一个(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;
    }
}

4
不错的解决方案;但我建议使用框架中的Action<T>而不是自己编写(并维护)代码。 - Fredrik Mörk
是的,这基本上是完成此操作的标准方式。需要这样做的原因是textBox1.Text只能在创建文本框的线程上更改 - 调用是返回到该线程的过程。 - Charlie Salts
@Fredrik:你说得没错,但这个例子更常见,很大程度上是因为它存在的时间更长。任何一种方法都可以。 - Charlie Salts

4
我一直觉得这篇文章对于这个特定问题非常有帮助。点此查看 在你的示例中,你正在尝试从未创建控件的线程修改各种控件。为了解决这个问题,请改用以下方法(假设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所说的,我已经注释掉了终止线程的调用。该线程将自行结束,没有必要以这种方式中止它。


因为他是最棒的,所以我给了他一个赞,之前没有人给过他(暗示,暗示)。 - B. Clay Shannon-B. Crow Raven

0

必须调用...但调用必须等待主线程,我是说这样做不会出错,但这并不完全是并行工作,如果你想同时进行多个进程,只需创建多个线程即可

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

0

使用线程控制是工作的最佳方式。

首先,您必须从单线程公寓线程中使用。

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

享受...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接