C#多线程

C#多线程 一多线程的几种方式 统一用于测试的模拟下载代码

private static void DownMethod(int id,string fileName) { Console.WriteLine("开始下载!文件ID:{0},文件名字{1}", id, fileName); for (int i = 0; i < 100; i+=10) { Thread.Sleep(10); Console.WriteLine("已经下载{0}", i); } Console.WriteLine("下载完毕!!!"); }

1:委托创建线程实现异步 实例1:Action委托创建线程
//多线程的第一种方式Action委托没有返回值 public static void ThreadMethod(int id, string fileName) { DownMethod(id,fileName); }static void Main(string[] args) {//方法一:使用Action委托来异步执行方法 Action fileDownAction = ThreadMethod; //前面的参数是委托所需要的参数,后面两个是固定的,一个是线程结束回调,一个是传入回调的参数 fileDownAction.BeginInvoke(1, "天才在左疯子在右", ar => { Console.WriteLine("线程结束回调"); Console.WriteLine(ar.AsyncState as string); //最后一个参数 fileDownAction.EndInvoke(ar); //这个方法用于获取此线程的返回值,由于Action委托没有返回值,所以不返回 }, "回调的参数"); Console.WriteLine("我是主线程"); Console.ReadKey(); }

结果
我是主线程 开始下载!文件ID:1,文件名字天才在左疯子在右 已经下载0 已经下载10 已经下载20 已经下载30 已经下载40 已经下载50 已经下载60 已经下载70 已经下载80 已经下载90 下载完毕!!! 线程结束回调 回调的参数

实例2:Func委托创建线程
public static string FuncThreadMethod(int id, string fileName) { DownMethod(id, fileName); return "异步方法的返回值!!!"; }static void Main(string[] args) {Func fileDownAction = FuncThreadMethod; //前面的参数是委托所需要的参数,后面两个是固定的,一个是线程结束回调,一个是传入回调的参数 fileDownAction.BeginInvoke(1, "天才在左疯子在右", ar => { Console.WriteLine("线程结束回调"); Console.WriteLine(ar.AsyncState as string); //最后一个参数 string result = fileDownAction.EndInvoke(ar); //这个方法用于获取此线程的返回值,由于Action委托没有返回值,所以不返回 Console.WriteLine(result); }, "回调的参数"); Console.WriteLine("我是主线程"); Console.ReadKey(); } }

我是主线程 开始下载!文件ID:1,文件名字天才在左疯子在右 已经下载0 已经下载10 已经下载20 已经下载30 已经下载40 已经下载50 已经下载60 已经下载70 已经下载80 已经下载90 下载完毕!!! 线程结束回调 回调的参数 异步方法的返回值!!!

2:Thread创建异步线程 实例1:普通的方式
//使用Thread方式创建的委托方法一定没有返回值,参数可有可无,但是有参数的话一定是object类型的 public static void AsyncWithThreadMethod(object obj) { Console.WriteLine("开始下载!文件名字{0}", obj as string); for (int i = 0; i < 100; i += 10) { Thread.Sleep(10); Console.WriteLine("已经下载{0}", i); } Console.WriteLine("下载完毕!!!"); }static void Main(string[] args) { //Thread创建线程的方式,Thread的构造参数一定是个无返回值的委托 Thread thread = new Thread(AsyncWithThreadMethod); thread.Start("天才在左疯子在右"); Console.WriteLine("我是主线程"); Console.ReadKey(); }

lambda表达式方式:
Thread thread = new Thread((obj) => { Console.WriteLine("开始下载!文件名字{0}", obj as string); for (var i = 0; i < 100; i += 10) { Thread.Sleep(10); Console.WriteLine("已经下载{0}", i); } Console.WriteLine("下载完毕!!!"); }); thread.Start("天才在左疯子在右");

但是这种方式如果想要传递一些复杂的或者多个参数就不好弄了,所以可以用另一种方式,自己新建一个类来处理
classMyThread { private int id; private string name; public MyThread(int id, string name) { this.id = id; this.name = name; }private void DownFileStart(object callback) { Action action = callback as Action; Console.WriteLine("开始下载!文件ID:{0},文件名字{1}", id, name); for (int i = 0; i < 100; i += 10) { Thread.Sleep(10); Console.WriteLine("已经下载{0}", i); } Console.WriteLine("下载完毕!!!"); action?.Invoke("下载完毕!!!!"); }//这个方法使用回调来处理线程执行完毕的结果 public void Start(Action callback) { Thread t = new Thread(DownFileStart); t.Start(callback); } }

使用:
MyThread mt = new MyThread(1, "aaa"); mt.Start(o => { Console.WriteLine("线程完成回调---->{0}", o); }); Console.WriteLine("我是主线程"); Console.ReadKey();

结果:
我是主线程 开始下载!文件ID:1,文件名字天才在左疯子在右 已经下载0 已经下载10 已经下载20 已经下载30 已经下载40 已经下载50 已经下载60 已经下载70 已经下载80 已经下载90 下载完毕!!! 线程完成回调---->下载完毕!!!!

前台线程和后台线程
前台线程:Thread创建的线程默认都是前台线程,前台线程不会跟随主线程的停止而停止,如果主线程提前停止,程序会跟着前台线程的停止而停止
后台线程:使用线程池创建的线程都是后台线程,不会被更改,主线程停止了后台线程也就随之停止了,Thread可以设置IsBackground = true; 来设置为后台线程
3:线程池创建线程 线程池类ThreadPool是静态类,无法被声明和new,只能通过类名来调用里面的静态方法
一般通过线程池创建线程的方法如下:
ThreadPool.QueueUserWorkItem(state => { Console.WriteLine("创建线程池的线程,参数:{0},线程ID:{1}", state as string, Thread.CurrentThread.ManagedThreadId); }, "11");

一般来说线程池用于处理多个耗时较为少的任务,不推荐处理长时间任务
4:任务(Task) (模拟线程代码)
private static void DownMethod(int id, string fileName) { Console.WriteLine("开始下载!文件ID:{0},文件名字{1}", id, fileName); for (int i = 0; i < 100; i += 10) { Thread.Sleep(10); Console.WriteLine("已经下载{0}", i); } Console.WriteLine("下载完毕!!!"); }//多线程的第一种方式Action委托没有返回值 public static string ThreadMethod() { DownMethod(1, "天才在左疯子在右"); return "1111"; } public static void SaveInFile(Task task) { Console.WriteLine("正在存储文件"); Thread.Sleep(1000); Console.WriteLine("存储完毕!!!"); }

任务有两种创建方式
  1. 泛型代表返回值
Task t = new Task(ThreadMethod); t.Start();

TaskFactory tf = new TaskFactory(); tf.StartNew(ThreadMethod);

连续任务 当任务1依赖于任务2的时候那就需要任务2执行完毕之后执行任务1,这个时候用连续任务
TaskFactory tf = new TaskFactory(); var startNew = tf.StartNew(ThreadMethod); //主要是这句话 var startNew2 = startNew.ContinueWith(SaveInFile);

任务的层次结构 【C#多线程】如果在一个任务中再次调用一个任务,那么内部这个任务就是外边的任务的子任务,如果子任务没有执行完毕而父任务执行完毕的话,则父任务的状态是WaitingForChildrenToComplete,只有子任务执行完了,父任务的状态就变成了RunToComplete
4:线程争用问题(同步锁) 测试类:
/// /// 测试线程争用问题 /// class MyThreadObject { //用于测试多线程争用同一变量的变量 private int state = 5; public void ChangeState() { state++; if (state == 5) { Console.WriteLine("state =5"); //正常来讲是永远不会执行这一句的 } state = 5; //当其中一个线程执行到这一句的时候另一个线程刚执行到if (state == 5),这时就会打印 } }

主函数:
class Program { static void ChangeMyThreadState(object obj) {var myThread = obj as MyThreadObject; while (true) { myThread?.ChangeState(); } }private static void Main(string[] args) { MyThreadObject myThread = new MyThreadObject(); Thread t1 = new Thread(ChangeMyThreadState); t1.Start(myThread); //只有这一个线程是没有问题的 Thread t2 = new Thread(ChangeMyThreadState); t2.Start(myThread); //加上这个线程就会出问题, Console.WriteLine("我是主线程"); Console.ReadKey(); } }

结果是打印了好多。
解决方法是加同步锁
static void ChangeMyThreadState(object obj) { var myThread = obj as MyThreadObject; while (true) { if (myThread == null) continue; lock (myThread) //像系统申请锁定需要调用的对象,如果另一个线程执行到这发现被锁定了则会等待锁定结束,然后继续执行 { myThread.ChangeState(); //在同一时刻只有一个线程执行 } } }

注意:lock只能锁定对象(引用类型)
4:线程死锁
class Program { static MyThreadObject myThread = new MyThreadObject(); static MyThreadObject myThread1 = new MyThreadObject(); static void ChangeMyThreadState(object obj) { while (true) { lock (myThread) //像系统申请锁定需要调用的对象,如果另一个线程执行到这发现被锁定了则会等待锁定结束,然后继续执行 { lock (myThread1) { myThread.ChangeState(); //在同一时刻只有一个线程执行 Console.WriteLine("0000"); } } } } static void ChangeMyThreadState1(object obj) { while (true) { lock (myThread1) //像系统申请锁定需要调用的对象,如果另一个线程执行到这发现被锁定了则会等待锁定结束,然后继续执行 { lock (myThread) { myThread.ChangeState(); //在同一时刻只有一个线程执行 Console.WriteLine("11111"); } } } }private static void Main(string[] args) { Thread t1 = new Thread(ChangeMyThreadState); t1.Start(); //只有这一个线程是没有问题的 Thread t2 = new Thread(ChangeMyThreadState1); t2.Start(); //加上这个线程就会出问题, Console.WriteLine("我是主线程"); Console.ReadKey(); } }

执行结果只打印了10几条,就停止了

    推荐阅读