注意:
1、不管是哪种多线程方法,在线程中访问共享资源的时候一定要用lock锁起来!不然会由于冲突产生各种奇奇怪怪的问题。
2、委托(含拉姆达表达式)中调用的方法,其参数如果是变量,它的值取决于运行的那一刻内存里的值。如果希望在创建任务的时候值是固定的,必须用object参数state作为创建任务的状态,把值给进去。(也就是《编译原理》中的“编译时”和“运行时”的差别)
3、而对于委托的begininvoke方法,里面的回调参数,是IAsyncResult类型。需要用它的“AsyncState”属性(object类型)获取传入的的内容(这一点跟代码提示的内容不一样)。
如果不太理解,看例子:
对于第一点
1 public partial class Form1 : Form 2 { 3 public Form1() 4 { 5 InitializeComponent(); 6 pBar1.Maximum = 200; 7 pBar1.Step = 1; 8 CheckForIllegalCrossThreadCalls = false; 9 } 10 11 private void button1_Click(object sender, EventArgs e) 12 { 13 for (int i = 1; i <=100; i++) 14 { 15 for (int j = 80; j <=81; j++) 16 { 17 ThreadPool.QueueUserWorkItem(tryconnect, new MyData(i, j)); 18 } 19 } 20 } 21 void tryconnect(object o) 22 { 23 int i, j; 24 i = ((MyData)o).x; 25 j = ((MyData)o).y; 26 TcpClient tcp = new TcpClient(); 27 IAsyncResult async = tcp.BeginConnect(IPAddress.Parse($"192.168.1.{i}"), j, null, null); 28 async.AsyncWaitHandle.WaitOne(1000); 29 lock(listBox1) 30 if (async.IsCompleted) 31 { 32 listBox1.Items.Add($"192.168.1.{i}:{j} is open."); 33 } 34 else 35 { 36 listBox1.Items.Add($"192.168.1.{i}:{j} is closed."); 37 } 38 //pBar1.PerformStep(); 39 tcp.Close(); 40 lock (pBar1) 41 pBar1.PerformStep(); 42 } 43 44 private void button2_Click(object sender, EventArgs e) 45 { 46 Close(); 47 } 48 } 49 class MyData 50 { 51 public int x, y; 52 public MyData(int a,int b) 53 { 54 x = a; 55 y = b; 56 } 57 }
对于这种密集的异步线程,再调用异步tcp连接,如果没有第29行的lock,在vs下调试时关闭窗口的时候就有很大概率出现下图的错误:
争夺资源越激烈概率越大,像代码中那样200次连接,已经是必然发生了。4次连接,大概25%左右发生概率。
编译完成的应用直接打开,退出的时候会出现一个错误框,一闪而逝,不注意看不到。
貌似不影响用户使用,但毕竟是隐患。