• C#异步多线程


    一、异步调用的方法

    Action委托的BeginInvoke调用实现的就是异步调用。

    1. Action<string> action = fun;
    2. for (int i = 0; i < 5; i++)
    3. {
    4. action.Invoke(i.ToString());
    5. }
    1. Action<string> action = fun;
    2. AsyncCallback callback = ar =>
    3. {
    4. Console.WriteLine(ar.AsyncState);
    5. Console.WriteLine($"程序完成,{Thread.CurrentThread.ManagedThreadId}");
    6. };
    7. action.BeginInvoke("文豪",callback,"hello Word");

    其中action.BeginInvoke调用中的:第一个参数为fun函数的传参

                                                            第二个参数为叫AsyncCallback的委托,以IAsyncResult类型的ar为参数传递一个函数(再调用第一个参数成功后,自动调用第二个委托)用来当作监控,一般输出”程序完成“,保证了一个函数在异步完成之后执行。

                                                            第三个参数为一个object类型的任何数据,可以以ar.AsyncState的方式查看。

    fun函数如下

    1. public void fun(string name)
    2. {
    3. Thread.Sleep(1000);
    4. Console.WriteLine(
    5. $"****Start,name: {name},
    6. 线程{Thread.CurrentThread.ManagedThreadId},
    7. {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}"
    8. );
    9. long Result = 0;
    10. for (int i = 0; i < 100000000; i++)
    11. {
    12. Result += i;
    13. }
    14. Console.WriteLine(
    15. $"****End,name:{name},
    16. 线程:{Thread.CurrentThread.ManagedThreadId},
    17. {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}"
    18. );
    19. }

    如果想异步调用带有返回值的函数想获得返回值时

    1. Func<int> func = HHH;
    2. var ir =func.BeginInvoke(null, null); //这里的ir是异步调用结果,描述异步调用的
    3. int num = func.EndInvoke(ir); //通过此函数可以拿到返回值
    4. Console.WriteLine(num);

    也可以放在回调里面

    1. Func<int> func = HHH;
    2. var ir = func.BeginInvoke(ar => { Console.WriteLine(func.EndInvoke(ar)); }, null);

    回调函数ar就是ir,返回的是异步调用的结果,再把ar当作参数传进给回调函数,ar信息表示异步调用完成后再执行回调函数。

    二、线程顺序(但是在异步调用中,程序的执行是无序的。为了保证程序的可用性,就需要找到让各个线程按照我们想执行的顺序执行)。

    1、等待

    1. var ar = action2.BeginInvoke(asyncResult, null, null);
    2. ar.AsyncWaitHandle.WaitOne(); //让程序等待,直到程序完成后再执行下一条命令(无延迟)
    3. ar.AsyncWaitHandle.WaitOne(-1);//一直等待
    4. ar.AsyncWaitHandle.WaitOne(1000);//最多等待1秒钟,做超时控制,微服务框架,如果接口超时就换接口或返回结果。

    2、使用Task

    3、await,async(就是强迫把await task的下一条必须使用task中最后执行的线程,当这个线程结束后,再使用这个线程完成下一条函数)作用

    1. public async Task AwaitFun()
    2. {
    3. Console.WriteLine($"This fun start 线程id:{Thread.CurrentThread.ManagedThreadId}");
    4. Task task = Task.Run(() => {
    5. Console.WriteLine($"This task start 线程id{Thread.CurrentThread.ManagedThreadId}");
    6. Thread.Sleep(1000);
    7. Console.WriteLine($"This task end 线程id:{Thread.CurrentThread.ManagedThreadId}");
    8. });
    9. await task;
    10. Console.WriteLine($"This fun end 线程id:{Thread.CurrentThread.ManagedThreadId}");
    11. }

     几乎等同于

    1. public void CallBackFun()
    2. {
    3. Console.WriteLine($"This fun start 线程id:{Thread.CurrentThread.ManagedThreadId}");
    4. Task task = Task.Run(() => {
    5. Console.WriteLine($"This task start 线程id{Thread.CurrentThread.ManagedThreadId}");
    6. Thread.Sleep(1000);
    7. Console.WriteLine($"This task end 线程id:{Thread.CurrentThread.ManagedThreadId}");
    8. });
    9. task.ContinueWith( test => { Console.WriteLine($"This fun end 线程id:
    10. {Thread.CurrentThread.ManagedThreadId}");
    11. });
    12. }

    三、线程安全

    在一段代码中,单线程和多线程执行结果不一致,就表明有线程安全问题 

    例:在循环中给List数组赋值就会出现安全问题。

    List是个数组结构,在内存上是连续摆放那个的,假如同一时刻,去增加一个数据,都是操作同一个内存位置,2个cpu同时发了命令,内存先执行一个再执行一个,就会出现覆盖。 

    1. List<int> intList = new List<int>();
    2. for (int i = 0; i < 10000; i++)
    3. {
    4. Task.Run(()=>
    5. {
    6. intList.Add(i);
    7. });
    8. }
    9. Console.WriteLine(intList.Count());

    此时Count出来的个数一定小于10000。为了解决这种数据丢失的安全问题就得使用锁。

    在类中添加上锁(注意:如果两个需要并发的程序内部不可以用同一个锁,会导致两个程序之间阻塞,所以尽量不要搞public的锁,说不定就被其他类调用了)

    private static readonly object LOCK = new object();
    1. List<int> intList = new List<int>();
    2. for (int i = 0; i < 10000; i++)
    3. {
    4. Task.Run(()=>
    5. {
    6. lock (LOCK)
    7. {
    8. intList.Add(i);
    9. }
    10. });
    11. }
    12. Console.WriteLine(intList.Count());

    加lock就能解决线程安全问题---就是单线程化---lock就是保证方法块任意时刻只有一个线程能进去,其他线程就排队---单线程化。

    Lock原理是一个语法糖。等价于Monitor,作用就是锁住一个变量的引用地址,不允许他被赋值,等到占用结束才能被下一个线程修改值。

    相当于

    1. List<int> intList = new List<int>();
    2. for (int i = 0; i < 10000; i++)
    3. {
    4. Task.Run(()=>
    5. {
    6. Monitor.Enter(LOCK);
    7. intList.Add(i);
    8. Monitor.Exit(LOCK);
    9. });
    10. }
    11. Console.WriteLine(intList.Count());


     

  • 相关阅读:
    面试必问:Redis 如何实现库存扣减操作?
    CSS篇十——(1)
    Python使用pillow库往图片上写入文字或覆盖另一张图片
    二、鼎捷T100总账管理之核算项管理篇
    如何使用 ABAP 代码发送邮件到指定邮箱
    认知负担的挑战与平台工程的机遇
    Linux进程控制
    数据分析-numpy2
    交叉编译是什么?为什么要交叉编译?
    [Linux](6)进程的概念,查看进程,创建子进程,进程状态,进程优先级
  • 原文地址:https://blog.csdn.net/m0_60939437/article/details/126509897