using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace 线程同步_线程异步 { //打印类 public class Printer { //打印数字的方法 public void PrintNumbers() { Console.WriteLine("-> {0} 正在执行打印任务,开始打印数字:", Thread.CurrentThread.Name); for (int i = 0; i < 10; i++) { Random r = new Random(); //为了增加冲突的几率及,使各线程各自等待随机的时长 Thread.Sleep(2000 * r.Next(5)); //打印数字 Console.Write("{0} ", i); } Console.WriteLine(); } } //打印类 public class Printer2 { public void PrintNumbers() { //使用lock关键字,锁定d的代码是线程安全的 lock (this) { Console.WriteLine("-> {0} 正在执行打印任务,开始打印数字:", Thread.CurrentThread.Name); for (int i = 0; i < 10; i++) { Random r = new Random(); //为了增加冲突的几率及,使各线程各自等待随机的时长 Thread.Sleep(2000 * r.Next(5)); //打印数字 Console.Write("{0} ", i); } Console.WriteLine(); } } } //也可以使用System.Threading命名空间下的Monitor类进行同步,两者内涵是一样的,但Monitor类更灵活,这里就不在做过多的探讨,代码如下: //打印类 public class Printer3 { public void PrintNumbers() { Monitor.Enter(this); try { Console.WriteLine("-> {0} 正在执行打印任务,开始打印数字:", Thread.CurrentThread.Name); for (int i = 0; i < 10; i++) { Random r = new Random(); //为了增加冲突的几率及,使各线程各自等待随机的时长 Thread.Sleep(2000 * r.Next(5)); //打印数字 Console.Write("{0} ", i); } Console.WriteLine(); } finally { Monitor.Exit(this); } } } class Program { //定义一个指向包含两个int型参数、返回值为int型的函数的委托 public delegate int AddOp(int x, int y); static void Main(string[] args) { #region 线程demo1 //Console.WriteLine("************** 显示当前线程的相关信息 *************"); ////声明线程变量并赋值为当前线程 ////Thread.CurrentThread得到的是执行当前代码的线程。 //Thread primaryThread = Thread.CurrentThread; ////赋值线程的名称 //primaryThread.Name = "主线程"; ////显示线程的相关信息 //Console.WriteLine("线程的名称:{0}", primaryThread.Name); //Console.WriteLine("线程是否启动?:{0}", primaryThread.IsAlive); //Console.WriteLine("线程的优先级{0}", primaryThread.Priority); //Console.WriteLine("线程的状态{0}", primaryThread.ThreadState); //Console.ReadLine(); #endregion //前台线程与后台线程。前台线程能阻止应用程序的终止,既直到所有前台线程终止后才会彻底关闭应用程序。而对后台线程而言,当所有前台线程终止时,后台线程会被自动终止,不论后台线程是否正在执行任务。默认情况下通过Thread.Start()方法创建的线程都自动为前台线程,把线程的属性IsBackground设为true时就将线程转为后台线程。 #region 异步调用线程 创建一个次线程执行打印数字的任务,而主线程则干其他的事,两者同时进行,互不干扰。 Demo2 //Console.WriteLine("************* 两个线程同时工作 *****************"); ////主线程,因为获得的是当前在执行Main()的线程 //Thread primaryThread = Thread.CurrentThread; //primaryThread.Name = "主线程"; //Console.WriteLine("-> {0} 在执行主线程 Main()。", Thread.CurrentThread.Name); ////次线程,该线程指向 PrintNumbers()方法 //Thread SecondThread = new Thread(new ThreadStart(PrintNumbers)); //// Thread SecondThread = new Thread(new ThreadStart(PrintNumbers)); 这一句做个解释。其实 ThreadStart 是 System.Threading 命名空间下的一个委托,其声明是 public delegate void ThreadStart(),指向不带参数、返回值为空的方法。所以当使用 ThreadStart 时,对应的线程就只能调用不带参数、返回值为空的方法。那非要指向含参数的方法呢?在System.Threading命名空间下还有一个ParameterizedThreadStart 委托,其声明是 public delegate void ParameterizedThreadStart(object obj),可以指向含 object 类型参数的方法,这里不要忘了 object 可是所有类型的父类哦,有了它就可以通过创建各种自定义类型,如结构、类等传递很多参数了,这里就不再举例说明了。 //SecondThread.Name = "次线程"; ////次线程开始执行指向的方法 //SecondThread.Start(); ////同时主线程在执行主函数中的其他任务 //Console.WriteLine("正在执行主函数中的任务。。。。", "主线程在工作..."); //Console.ReadLine(); #endregion #region Demo3 并发问题 ////,主线程产生的10个线程同时访问同一个对象实例printer的方法PrintNumbers(),由于没有锁定共享资源(注意,这里是指控制台),所以在PrintNumbers()输出到控制台之前,调用PrintNumbers()的线程很可能被挂起,但不知道什么时候(或是否有)挂起,导致得到不可预测的结果。 //Console.WriteLine("********* 并发问题演示 *********"); ////创建一个打印对象实例 //Printer printer = new Printer(); ////声明一个含5个线程对象的数组 //Thread[] threads = new Thread[10]; //for (int i = 0; i < 10; i++) //{ // //将每一个线程都指向printer的PrintNumbers()方法 // threads[i] = new Thread(new ThreadStart(printer.PrintNumbers)); // //给每一个线程编号 // threads[i].Name = i.ToString() + "号线程"; //} ////开始执行所有线程 //foreach (Thread t in threads) // t.Start(); //Console.ReadLine(); #endregion #region Demo4 线程同步 ////线程同步的访问方式也称为阻塞调用,即没有执行完任务不返回,线程被挂起。可以使用C#中的lock关键字,在此关键字范围类的代码都将是线程安全的。lock关键字需定义一个标记,线程进入锁定范围是必须获得这个标记。当锁定的是一个实例级对象的私有方法时使用方法本身所在对象的引用就可以了,将上面例子中的打印类Printer稍做改动,添加lock关键字, //Console.WriteLine("********* 并发问题演示 *********"); ////创建一个打印对象实例 ////Printer2 printer = new Printer2(); //Printer3 printer = new Printer3(); ////声明一个含5个线程对象的数组 //Thread[] threads = new Thread[10]; //for (int i = 0; i < 10; i++) //{ // //将每一个线程都指向printer的PrintNumbers()方法 // threads[i] = new Thread(new ThreadStart(printer.PrintNumbers)); // //给每一个线程编号 // threads[i].Name = i.ToString() + "号线程"; //} ////开始执行所有线程 //foreach (Thread t in threads) // t.Start(); ////Console.WriteLine("heihei"); //Console.ReadLine(); #endregion #region Demo5 委托 ////创建一个指向包含两个int型参数、返回值为int型的函数的委托 ////创建一个指向Add()方法的AddOp对象p //AddOp pAddOp = new AddOp(Add); ////使用委托间接调用方法Add() //Console.WriteLine("10 + 5 = {0}", pAddOp(10, 5)); //Console.ReadLine(); #endregion #region Demo6 线程异步 ////观察上面的例子会发现,直接使用委托实例 pAddOp(10, 5) 就调用了求和方法 Add()。很明显,这个方法是由主线程执行的。然而,委托类型中还有另外两个方法——BeginInvoke()和EndInvoke() //Console.WriteLine("******* 委托异步线程 两个线程“同时”工作 *********"); ////显示主线程的唯一标示 //Console.WriteLine("调用Main()的主线程的线程ID是:{0}.", Thread.CurrentThread.ManagedThreadId); ////将委托实例指向Add2()方法 //AddOp pAddOp = new AddOp(Add2); ////AddOp pAddOp = Add2; ////开始委托次线程调用。委托BeginInvoke()方法返回的类型是IAsyncResult, ////包含这委托指向方法结束返回的值,同时也是EndInvoke()方法参数 //IAsyncResult iftAR = pAddOp.BeginInvoke(10, 10, null, null); //Console.WriteLine("nMain()方法中执行其他任务........n"); //int sum = pAddOp.EndInvoke(iftAR); //Console.WriteLine("10 + 10 = {0}.", sum); //Console.ReadLine(); #endregion #region Demo7 线程同步 //委托中的线程同步主要涉及到上面使用的pAddOp.BeginInvoke(10, 10, null, null)方法中后面两个为null的参数,具体的可以参考相关资料。这里代码如下,解释见代码注释: Console.WriteLine("******* 线程同步,“阻塞”调用,两个线程工作 *********"); Console.WriteLine("Main() invokee on thread {0}.", Thread.CurrentThread.ManagedThreadId); //将委托实例指向Add()方法 AddOp pAddOp = new AddOp(Add3); IAsyncResult iftAR = pAddOp.BeginInvoke(10, 10, null, null); //判断委托线程是否执行完任务, //没有完成的话,主线程就做其他的事 while (!iftAR.IsCompleted) { Console.WriteLine("Main()方法工作中......."); Thread.Sleep(1000); } //获得返回值 int answer = pAddOp.EndInvoke(iftAR); Console.WriteLine("10 + 10 = {0}.", answer); Console.ReadLine(); #endregion } //求和的函数 static int Add(int x, int y) { int sum = x + y; return sum; } //求和方法 static int Add2(int x, int y) { //指示调用该方法的线程ID,ManagedThreadId是线程的唯一标示 Console.WriteLine("调用求和方法 Add()的线程ID是: {0}.", Thread.CurrentThread.ManagedThreadId); //模拟一个过程,停留5秒 Thread.Sleep(5000); int sum = x + y; return sum; } //求和方法 static int Add3(int x, int y) { //指示调用该方法的线程ID,ManagedThreadId是线程的唯一标示 Console.WriteLine("调用求和方法 Add()的线程ID是: {0}.", Thread.CurrentThread.ManagedThreadId); //模拟一个过程,停留5秒 Thread.Sleep(5000); int sum = x + y; return sum; } //打印数字的方法 static void PrintNumbers() { Console.WriteLine("-> {0} 在执行打印数字函数 PrintNumber()", Thread.CurrentThread.Name); Console.WriteLine("打印数字: "); for (int i = 0; i < 10; i++) { Console.Write("{0}, ", i); //Sleep()方法使当前线程挂等待指定的时长在执行,这里主要是模仿打印任务 Thread.Sleep(2000); } Console.WriteLine(); } } }
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace BackgroundWorker组件_线程异步 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } //BackgroundWorker组件 //BackgroundWorker组件位于工具箱中,用于方便的创建线程异步的程序 private void button1_Click(object sender, EventArgs e) { try { ////获得输入的数字 int numOne = int.Parse(this.textBox1.Text); int numTwo = int.Parse(this.textBox2.Text); ////实例化参数类 AddParams args = new AddParams(numOne, numTwo); //调用RunWorkerAsync()生成后台线程,同时传入参数 this.backgroundWorker1.RunWorkerAsync(args); } catch (Exception ex) { MessageBox.Show(ex.Message); //throw; } } //backgroundWorker新生成的线程开始工作 private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { //获取传入的AddParams对象 AddParams args = (AddParams)e.Argument; //停留5秒,模拟耗时任务 //Thread.Sleep(5000); Thread.Sleep(10000); //返回值 e.Result = args.a + args.b; } //当backgroundWorker1的DoWork中的代码执行完后会触发该事件 //同时,其执行的结果会包含在RunWorkerCompletedEventArgs参数中 private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { //显示运算结果 MessageBox.Show("运行结果为:" + e.Result.ToString(), "结果"); } } //参数类,这个类仅仅起到一个记录并传递参数的作用 class AddParams { public int a, b; public AddParams(int numb1, int numb2) { a = numb1; b = numb2; } } //注意,在计算结果的同时,窗体可以随意移动,也可以重新在文本框中输入信息,这就说明主线程与backgroundWorker组件生成的线程是异步的。 }