Adresse originale : Cliquez pour ouvrir le lien
[Résumé]Cet article présente C#
Control.Invoke pour le développement multithread WinForm et un exemple de code détaillé est fourni à titre de référence.
Ci-dessous, nous vous donnerons une introduction aux problèmes multithread auxquels il faut prêter attention lors de l'utilisation d'Invoke dans Windows Form. logiciel.
Tout d'abord, quels types d'opérations faut-il envisager en utilisant le multi-threading ? La règle générale est que le thread chargé d'interagir avec l'utilisateur (ci-après dénommé le thread UI) doit rester fluide lorsque l'API appelée par le thread UI peut provoquer un temps de blocage de plus de 30 millisecondes (comme l'accès à ultra-). périphériques lents tels que CD-ROM, appels à distance, etc.), vous devriez envisager d'utiliser le multithreading. Pourquoi 30 millisecondes ? Le concept de 30 millisecondes est un décalage détectable par l'œil humain, qui équivaut à peu près à la durée d'une image dans un film et ne doit pas dépasser 100 millisecondes au maximum.
Deuxièmement, le multithreading le plus pratique et le plus simple consiste à utiliser un pool de threads. Le moyen le plus simple d’exécuter du code via des threads dans un pool de threads consiste à utiliser des appels de délégués asynchrones. Notez que les appels de délégués sont généralement effectués de manière synchrone. Veuillez utiliser la méthode BeginInvoke, afin que la méthode à appeler puisse être mise en file d'attente dans le pool de threads pour attendre le traitement, et que le flux du programme revienne immédiatement à l'appelant (ici le thread de l'interface utilisateur). , et l'appel Il n'y aura donc pas de blocage.
En regardant l'exemple ci-dessous, nous constatons qu'il n'est pas très compliqué d'utiliser le pool de threads pour exécuter du code de manière asynchrone. Ici, nous utilisons le délégué System.Windows.Forms.MethodInvoker pour effectuer des appels asynchrones. Notez que le délégué MethodInvoker n'accepte pas les paramètres de méthode. Si vous devez transmettre des paramètres à des méthodes exécutées de manière asynchrone, veuillez utiliser d'autres délégués ou vous devez les définir vous-même.
private void StartSomeWorkFromUIThread () { // 我们要做的工作相对UI线程而言台慢了,用下面的方法异步进行处理 MethodInvoker mi = new MethodInvoker(RunsOnWorkerThread);//这是入口方法 mi.BeginInvoke(null, null); // 这样就不会阻塞 } // 缓慢的工作在此方法内进行处理,使用线程池里的线程 private void RunsOnWorkerThread() { DoSomethingSlow(); }
Pour résumer la méthode ci-dessus, pour le fil de discussion de l'interface utilisateur, il s'agit en fait de : 1. Passer un appel, 2. Revenez immédiatement, le processus en cours spécifique est ignoré, de sorte que le thread de l'interface utilisateur ne soit pas bloqué. Cette approche est importante et nous l’aborderons en profondeur ci-dessous. En plus des méthodes ci-dessus, il existe d'autres façons d'utiliser le pool de threads. Bien sûr, si vous êtes satisfait, vous pouvez également créer vos propres threads.
第三,在Windows Form中使用多线程的,最重要的一条注意事项是,除了创建控件的线程以外,绝对不要在任何其他线程里面调用控件的成员(只有极个别情况例外),也就是说控件属于创建它的线程,不能从其他线程里面访问。这一条适用于所有从System.Windows.Forms.Control派生的控件(因此可以说是几乎所有控件),包括Form控件本身也是。举一反三,我们很容易得出这样的结论,控件的子控件必须由创建控件的线程来创建,比如一个表单上的按钮,比如由创建表单的线程来创建,因此,一个窗口中的所有控件实际上都活在同一个线程之中。在实际编程时,大多数的软件的做法都是让同一线程负责全部的控件,这就是我们所说的UI线程。看下面的例子:
// 这是由UI线程定义的Label控件 private Label lblStatus; // 以下方法不在UI线程上执行 private void RunsOnWorkerThread() { DoSomethingSlow(); lblStatus.Text = "Finished!"; // 这是错的 }
我们要特别提醒大家,很多人刚开始的时候都会使用以上的方法来访问不在同一个线程里的控件(包括笔者本人),而且在1.0版.Net 框架上似乎没有发现问题,但是这根本就是错的,更糟糕的是,程序员在这里不会得到任何错误提示,一开始就上当受骗,之后会莫明其妙地发现其他错误,这就是Windows Form多线程编程的痛苦所在。笔者试过花很多时间来Debug自己写的Splash窗口突然消失的问题,结果还是失败了:笔者在软件的引导过程中,用另外一个线程里创建了一个Splash窗口来显示欢迎信息,然后尝试把主线程里引导的状态直接写入到Splash窗口上的控件中,开始还OK,可是过一会Splash窗口就莫明其妙消失了。
理解了这一点,我们应该留意到,有时候即使没有用System.Threading.Thread来显式创建一个线程,我们也可能因为使用了异步委托的BeginInvoke方法来隐式创建了线程(从线程池里),在这种线程里也同样不能调用UI线程所创建的控件的成员。
第四,由于上述限制,我们可能会感到很不方便,的确,当我们利用一个新创建的线程来执行某些花时间的运算时,怎样知道运算进度如何并通过UI反映给用户呢?解决方法很多!比如熟悉多线程编程的用户很快会想到,我们采用一些低级的同步方法,工作者线程把状态保存到一个同步对象中,让UI线程轮询(Polling)该对象并反馈给用户就可以了。不过,这还是挺麻烦的,实际上不用这样做,Control类(及其派生类)对象有一个Invoke方法很特别,这是少数几个不受线程限制的成员之一。我们前面说到,绝对不要在任何其他线程里面调用非本线程创建的控件的成员时,也说了“只有极个别情况例外”,这个Invoke方法就是极个别情况之一----Invoke方法可以从任何线程里面调用。下面我们来讲解Invoke方法。
Invoke方法的参数很简单,一个委托,一个参数表(可选),而Invoke方法的主要功能就是帮助你在UI线程(即创建控件的线程)上调用委托所指定的方法。Invoke方法首先检查发出调用的线程(即当前线程)是不是UI线程,如果是,直接执行委托指向的方法,如果不是,它将切换到UI线程,然后执行委托指向的方法。不管当前线程是不是UI线程,Invoke都阻塞直到委托指向的方法执行完毕,然后切换回发出调用的线程(如果需要的话),返回。注意,使用Invoke方法时,UI线程不能处于阻塞状态。以下MSDN里关于Invoke方法的说明:
“控件上有四种方法可以安全地从任何线程进行调用:Invoke、BeginInvoke、EndInvoke 和 CreateGraphics。对于所有其他方法调用,则应使用调用 (invoke) 方法之一封送对控件的线程的调用。 委托可以是 EventHandler 的实例,在此情况下,发送方参数将包含此控件,而事件参数将包含 EventArgs.Empty。委托还可以是 MethodInvoker 的实例或采用 void 参数列表的其他任何委托。调用 EventHandler 或 MethodInvoker 委托比调用其他类型的委托速度更快。”
好了,说完Invoke,顺便说说BeginInvoke,毫无疑问这是Invoke的异步版本(Invoke是同步完成的),不过大家不要和上面的System.Windows.Forms.MethodInvoker委托中的BeginInvoke混淆,两者都是利用不同线程来完成工作,但是控件的BeginInvoke方法总是使用UI线程,而其他的异步委托调用方法则是利用线程池里的线程。相对Invoke而言,使用BeginInvoke稍稍麻烦一点,但还是那句话,异步比同步效果好,尽管复杂些。比如同步方法可能出现这样一种死锁情况:工作者线程通过Invoke同步调用UI线程里的方法时会阻塞,而万一UI线程正在等待工作者线程做某件事时怎么办?因此,能够使用异步方法时应尽量使用异步方法。
下面我们利用所学到的知识来改写上面那个简单的例子:
// 这是由UI线程定义的Label控件 private Label lblStatus; // 以下方法不在UI线程上执行 private void RunsOnWorkerThread() { DoSomethingSlow(); // Do UI update on UI thread object[] pList = { this, System.EventArgs.Empty }; lblStatus.BeginInvoke( new System.EventHandler(UpdateUI), pList); } // 切换回UI线程执行的入口 private void UpdateUI(object o, System.EventArgs e) { //现在没问题了,使用Invoke使得线程总是回到UI线程,所以我们可以放心大胆地调用控件的成员了 lblStatus.Text = "Finished!"; }
第五,关于多线程编程还要考虑线程之间的同步问题、死锁和争用条件,有关这类问题的文章很多,我们就不赘述了
以上就是C# WinForm多线程开发(三) Control.Invoke 的内容,更多相关内容请关注PHP中文网(www.php.cn)!