一、异步调用的方法
Action委托的BeginInvoke调用实现的就是异步调用。
例
- Action<string> action = fun;
- for (int i = 0; i < 5; i++)
- {
- action.Invoke(i.ToString());
- }
- Action<string> action = fun;
- AsyncCallback callback = ar =>
- {
- Console.WriteLine(ar.AsyncState);
- Console.WriteLine($"程序完成,{Thread.CurrentThread.ManagedThreadId}");
- };
- action.BeginInvoke("文豪",callback,"hello Word");
其中action.BeginInvoke调用中的:第一个参数为fun函数的传参
第二个参数为叫AsyncCallback的委托,以IAsyncResult类型的ar为参数传递一个函数(再调用第一个参数成功后,自动调用第二个委托)用来当作监控,一般输出”程序完成“,保证了一个函数在异步完成之后执行。
第三个参数为一个object类型的任何数据,可以以ar.AsyncState的方式查看。
fun函数如下
- public void fun(string name)
- {
- Thread.Sleep(1000);
- Console.WriteLine(
- $"****Start,name: {name},
- 线程{Thread.CurrentThread.ManagedThreadId},
- {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}"
- );
- long Result = 0;
- for (int i = 0; i < 100000000; i++)
- {
- Result += i;
- }
- Console.WriteLine(
- $"****End,name:{name},
- 线程:{Thread.CurrentThread.ManagedThreadId},
- {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}"
- );
-
- }
如果想异步调用带有返回值的函数想获得返回值时
- Func<int> func = HHH;
- var ir =func.BeginInvoke(null, null); //这里的ir是异步调用结果,描述异步调用的
- int num = func.EndInvoke(ir); //通过此函数可以拿到返回值
- Console.WriteLine(num);
也可以放在回调里面
- Func<int> func = HHH;
- var ir = func.BeginInvoke(ar => { Console.WriteLine(func.EndInvoke(ar)); }, null);
回调函数ar就是ir,返回的是异步调用的结果,再把ar当作参数传进给回调函数,ar信息表示异步调用完成后再执行回调函数。
二、线程顺序(但是在异步调用中,程序的执行是无序的。为了保证程序的可用性,就需要找到让各个线程按照我们想执行的顺序执行)。
1、等待
- var ar = action2.BeginInvoke(asyncResult, null, null);
- ar.AsyncWaitHandle.WaitOne(); //让程序等待,直到程序完成后再执行下一条命令(无延迟)
- ar.AsyncWaitHandle.WaitOne(-1);//一直等待
- ar.AsyncWaitHandle.WaitOne(1000);//最多等待1秒钟,做超时控制,微服务框架,如果接口超时就换接口或返回结果。
2、使用Task
3、await,async(就是强迫把await task的下一条必须使用task中最后执行的线程,当这个线程结束后,再使用这个线程完成下一条函数)作用
- public async Task AwaitFun()
- {
- Console.WriteLine($"This fun start 线程id:{Thread.CurrentThread.ManagedThreadId}");
- Task task = Task.Run(() => {
- Console.WriteLine($"This task start 线程id{Thread.CurrentThread.ManagedThreadId}");
- Thread.Sleep(1000);
- Console.WriteLine($"This task end 线程id:{Thread.CurrentThread.ManagedThreadId}");
- });
- await task;
- Console.WriteLine($"This fun end 线程id:{Thread.CurrentThread.ManagedThreadId}");
- }
几乎等同于
- public void CallBackFun()
- {
-
- Console.WriteLine($"This fun start 线程id:{Thread.CurrentThread.ManagedThreadId}");
- Task task = Task.Run(() => {
- Console.WriteLine($"This task start 线程id{Thread.CurrentThread.ManagedThreadId}");
- Thread.Sleep(1000);
- Console.WriteLine($"This task end 线程id:{Thread.CurrentThread.ManagedThreadId}");
- });
- task.ContinueWith( test => { Console.WriteLine($"This fun end 线程id:
- {Thread.CurrentThread.ManagedThreadId}");
- });
-
-
- }
三、线程安全
在一段代码中,单线程和多线程执行结果不一致,就表明有线程安全问题
例:在循环中给List数组赋值就会出现安全问题。
List是个数组结构,在内存上是连续摆放那个的,假如同一时刻,去增加一个数据,都是操作同一个内存位置,2个cpu同时发了命令,内存先执行一个再执行一个,就会出现覆盖。
- List<int> intList = new List<int>();
- for (int i = 0; i < 10000; i++)
- {
- Task.Run(()=>
- {
- intList.Add(i);
- });
-
- }
- Console.WriteLine(intList.Count());
此时Count出来的个数一定小于10000。为了解决这种数据丢失的安全问题就得使用锁。
在类中添加上锁(注意:如果两个需要并发的程序内部不可以用同一个锁,会导致两个程序之间阻塞,所以尽量不要搞public的锁,说不定就被其他类调用了)
private static readonly object LOCK = new object();
- List<int> intList = new List<int>();
- for (int i = 0; i < 10000; i++)
- {
- Task.Run(()=>
- {
- lock (LOCK)
- {
- intList.Add(i);
- }
- });
- }
- Console.WriteLine(intList.Count());
加lock就能解决线程安全问题---就是单线程化---lock就是保证方法块任意时刻只有一个线程能进去,其他线程就排队---单线程化。
Lock原理是一个语法糖。等价于Monitor,作用就是锁住一个变量的引用地址,不允许他被赋值,等到占用结束才能被下一个线程修改值。
相当于
- List<int> intList = new List<int>();
- for (int i = 0; i < 10000; i++)
- {
- Task.Run(()=>
- {
- Monitor.Enter(LOCK);
- intList.Add(i);
- Monitor.Exit(LOCK);
- });
-
- }
- Console.WriteLine(intList.Count());