背景
web开发本来就是异步多线程,好学一些。C#不一样,做工具的时候,数据量过大会出现假死,所以考虑多线程。
学习笔记
1.C#多线程编程
2020.11.13
Threading in .NET and WinForms
委托:包含方法数组的数据类型。委托中的所有方法应具有相同的签名和相同的返回类型。委托的声明本身将定义委托的方法签名和返回类型。
public delegate void MyDelegate(int arg);
2020.11.16
1.1 Threading
1.1.2 Threading in .NET
要将函数添加到委托中,请创建委托的实例,然后将函数名称作为参数传递。
MyDelegate delInstance = new MyDelegate(Function1);
多播:
delInstance += new MyDelegate(Function2);
1.1.3 Creating Threads in .NET
创建线程:
1 class Test0 2 { 3 public static void Main() 4 { 5 Thread t = new Thread(new ThreadStart(Test0.MyFunction)); 6 t.Start(); 7 for (int i = 0; i < 100; i++) 8 Console.WriteLine("I am in Main Thread {0}", i); 9 } 10 11 public static void MyFunction() 12 { 13 for (int i = 0; i < 100; i++) 14 Console.WriteLine("I am in Different Thread {0}", i); 15 } 16 }
委托具有由编译器提供的名为BeginInvoke()的内置函数。此函数可用于在线程中执行函数。任何委托上的BeginInvoke调用都将在单独的线程中执行。
在下面的代码中,对委托实例的BeginInvoke调用在单独的线程中运行MyFunction。下面代码的输出将是控制台消息在主线程和委托线程之间交换(类似于使用Thread类)。请注意,无法通过BeginInvoke多播委托。
1 class Test1 2 { 3 public delegate void MyDelegate(int i, string str); 4 public static void Main() 5 { 6 MyDelegate delInstance = new MyDelegate(MyFunction); 7 delInstance.BeginInvoke(100, " I am in Delegate Thread", null, null); 8 for (int i = 0; i < 100; i++) 9 Console.WriteLine("I am in Main Thread {0}", i); 10 Console.ReadKey(); 11 } 12 public static void MyFunction(int count, string str) 13 { 14 for (int i = 0; i < count; i++) 15 Console.WriteLine(str + " {0}", i); 16 } 17 }
由于BeginInvoke是异步调用(仅触发另一个线程并继续运行下一行,而不等待完成另一个线程),因此它无法直接获取函数调用的返回值。因此,我们需要某种机制来收集返回值。 C#中BeginInvoke函数的实现返回IAsyncResult类型。必要时,此IAsyncResult可用于获取返回值。有一个名为EndInvoke的函数,它将此IAsynResult作为参数并收集返回值。因此,可以使用EndInvoke函数来收集返回值。
1 class Test2 2 { 3 public delegate bool MyDelegate(int i, string str); 4 public static void Main() 5 { 6 MyDelegate delInstance = new MyDelegate(MyFunction); 7 IAsyncResult ref1 = delInstance.BeginInvoke(100, "I am in Delegate Thread", null, null); 8 for (int i = 0; i < 100; i++) 9 Console.WriteLine("I am in Main Thread {0}", i); 10 bool status = delInstance.EndInvoke(ref1); 11 Console.ReadKey(); 12 } 13 public static bool MyFunction(int count, string str) 14 { 15 for (int i = 0; i < count; i++) 16 Console.WriteLine(str + " {0}", i); 17 return true; 18 } 19 }
在下面的代码中,对EndInvoke的调用将阻塞主线程,直到MyFunction完成。
如果线程已经完成执行,则对EndInvoke的调用将立即收集返回值。否则,它将等待线程完成并获得结果。
在调用BeginInvoke时,我们尚未创建任何线程,那么谁在创建线程?它如何在单独的线程中运行?
要回答这些问题,首先让我们知道什么是线程池。
1 class Test4 2 { 3 public delegate int MyDelegate(int i, string str); 4 public static void Main() 5 { 6 MyDelegate delInstance = new MyDelegate(MyFunction); 7 IAsyncResult iRef = delInstance.BeginInvoke(100, " I am in Delegate Thread", 8 null, null); 9 int val = delInstance.EndInvoke(iRef); 10 Console.ReadKey(); 11 } 12 public static int MyFunction(int count, string str) 13 { 14 int i; 15 for (i = 0; i < count; i++) 16 Console.WriteLine(str + " {0}", i); 17 return i; 18 } 19 }
1.1.4 Thread Pool
在运行时创建线程并非易事。对于较小的线程,大多数时间将花费在创建线程上,而不是在线程上执行功能。
CLR(公共语言运行时)在池中已经创建了一些线程,以便可以在需要时使用它们。这些线程称为线程池线程。如果我们在线程池中使用线程,则在线程完成时,该线程将不会被销毁。线程将在挂起状态下返回线程池。该挂起的线程可以重新用于其他目的。
BeginInvoke函数使用这些线程池线程来执行功能。使用线程池线程可以节省线程创建时间。
1.2 Threading in WinForms
1.2.1 WinForms Internals
1 private void Form1_Load(object sender, EventArgs e) 2 { 3 AddControl(); 4 } 5 void AddControl() 6 { 7 TextBox testBox1 = new TextBox(); 8 Controls.Add(testBox1); 9 }
1.2.2 Control.Invoke
1 public delegate void MyDelegate(); 2 3 private void Form1_Load(object sender, EventArgs e) 4 { 5 6 Thread t = new Thread(new ThreadStart(RunInThread)); 7 t.Start(); 8 } 9 void RunInThread() 10 { 11 MyDelegate delInstatnce = new MyDelegate(AddControl); 12 this.Invoke(delInstatnce); 13 MessageBox.Show("Hello"); 14 //Add your code that needs to be executed in separate thread 15 //except UI updation 16 } 17 void AddControl() 18 { 19 TextBox textBox1 = new TextBox(); 20 Controls.Add(textBox1); 21 } 22 }
1.2.3 Control.BeginInvoke
如前所述,Control类上的BeginInvoke方法将调用放置在发布消息队列中。该调用最终将由主线程执行,并且BeginInvoke方法不会像Invoke那样阻塞当前线程。将呼叫放在帖子消息队列中后,它将开始执行下一行。可以使用EndInvoke方法收集返回值。
1 public delegate void MyDelegate(); 2 3 private void Form1_Load(object sender, EventArgs e) 4 { 5 Thread t = new Thread(new ThreadStart(RunInThread)); 6 t.Start(); 7 } 8 void RunInThread() 9 { 10 MyDelegate delInstatnce = new MyDelegate(AddControl); 11 this.BeginInvoke(delInstatnce); 12 MessageBox.Show("Hello"); 13 //Add your code that needs to be executed in separate thread 14 //except UI updation 15 } 16 void AddControl() 17 { 18 TextBox textBox1 = new TextBox(); 19 Controls.Add(textBox1); 20 Thread.Sleep(10000); 21 }
1.2.4 Control.InvokeRequired
1 delegate void AddForm(); 2 3 private void Form1_Load(object sender, EventArgs e) 4 { 5 6 Thread t = new Thread(new ThreadStart(RunInThread)); 7 t.Start(); 8 } 9 void RunInThread() 10 { 11 AddControl(); 12 } 13 void AddControl() 14 { 15 if (this.InvokeRequired) 16 { //如果当前线程不是主线程,则返回true;如果当前线程是主线程,则返回false。 17 //因此,UI的任何更新都应通过InvokeRequired属性进行,这是一种安全的编码做法。 18 //调用者将不会处理Invoke方法,而是会在函数内部实现。 19 AddForm add = new AddForm(AddControl); 20 this.Invoke(add); 21 } 22 else 23 { 24 TextBox testBox1 = new TextBox(); 25 Controls.Add(testBox1); 26 } 27 28 }