• C# 并发编程 (异步编程与多线程)


    并发:同时做多件事情

    多线程:并发的一种形式,它采用多个线程来执行程序。

    并行处理:把正在执行的大量的任务分割成小块,分配给多个同时运行的线程。并行处理是多线程的一种,而多线程是并发的一种。

    异步编程:并发的一种形式,它采用 future 模式或回调(callback)机制,以避免产生不必要的 线程,异步编程的核心理念是异步操作:启动了的操作将会在一段时间后完成。这个操作 正在执行时,不会阻塞原来的线程。启动了这个操作的线程,可以继续执行其他任务。当 操作完成时,会通知它的 future,或者调用回调函数,以便让程序知道操作已经结束,

    async 和 await:这让异步编程变得几乎和同步(非并发)编程一样容易。await的作用:启动一个将会被执行的Task(该Task将会在新线程中执行),并立即返回,所以await所在的函数不会被阻塞,当Task完成后,继续执行await后面的代码。

     通常情况下,一个并发程序要使用多种技术。大多数程序至少使用了多线程(通过线程 池)和异步编程

    异步编程有两大好处。第一个好处是对于面向终端用户的 GUI 程序:异步编程提高了响应 能力。我们都遇到过在运行时会临时锁定界面的程序,异步编程可以使程序在执行任务时 仍能响应用户的输入。第二个好处是对于服务器端应用:异步编程实现了可扩展性。服务 器应用可以利用线程池满足其可扩展性,使用异步编程后,可扩展性通常可以提高一个数 量级。
    现代的异步 .NET 程序使用两个关键字:async 和 await。async 关键字加在方法声明上, 它的主要目的是使方法内的 await 关键字生效(为了保持向后兼容,同时引入了这两个关 键字)。如果 async 方法有返回值,应返回 Task<T>;如果没有返回值,应返回 Task。这些 task 类型相当于 future,用来在异步方法结束时通知主程序。

    1.Async/Await

    async 方法在开始时以同步方式执行。在 async 方法内部,await 关键字 对它的参数执行一个异步等待。它首先检查操作是否已经完成,如果完成了,就继续运行 (同步方式)。否则,它会暂停 async 方法,并返回,留下一个未完成的 task。一段时间后, 操作完成,async 方法就恢复运行。 不会阻塞UI线程 ,await方法完成自动的通知他 然后执行剩余的代码,异步是为了程序本身不卡 调用处还是异步的 调用处下面的代码还是会先执行 await实际上只是把主线程释放了,使用了其他线程代替他继续 异步不是为了让请求更快,而是为了可以处理更多请求

    异步有一个核心,是Task。而Task有一个方法,就是Wait,写法是Task.Wait()。所以,很多人把这个Wait和await混为一谈,这是错的。

    这个问题来自于Task。C#里,Task不是专为异步准备的,它表达的是一个线程,是工作在线程池里的一个线程。异步是线程的一种应用,多线程也是线程的一种应用。Wait,以及Status、IsCanceled、IsCompleted、IsFaulted等等,是给多线程准备的方法,跟异步没有半毛钱关系。当然你非要在异步中使用多线程的Wait或其它,从代码编译层面不会出错,但程序会。

    尤其,Task.Wait()是一个同步方法,用于多线程中阻塞等待。

    用Task.Wait()来实现同步方法中调用异步方法,这个用法本身就是错误的。 异步不是多线程,而且在多线程中,多个Task.Wait()使用也会死锁,也有解决和避免死锁的一整套方式。

    Task.Wait()是一个同步方法,用于多线程中阻塞等待,不是实现同步方法中调用异步方法的实现方式。

    在异步中,await表达的意思是:当前线程/方法中,await引导的方法出结果前,跳出当前线程/方法,从调用当前线程/方法的位置,去执行其它可能执行的线程/方法,并在引导的方法出结果后,把运行点拉回到当前位置继续执行;直到遇到下一个await,或线程/方法完成返回,跳回去刚才外部最后执行的位置继续执行。

    2.异步锁

    从 .NET Framework 4.5 开始,任何使用 async/await 进行修饰的方法,都会被认为是一个异步方法;实际上,这些异步方法都是基于队列的线程任务,从你开始使用 Task 去运行一段代码的时候,实际上就相当于开启了一个线程,默认情况下,这个线程数由线程池 ThreadPool 进行管理的。

    线程安全的访问方式可以通过lock来进行唯一线程限定,但如果使用await等待Task完成,则Task中不允许使用lock。

    因此采用另外一种方式完成

     

     调用

     3.ConcurrentQueue 队列

    锁的引入,带来了一定的开销和性能的损耗,并降低了程序的扩展性,而且还会有死锁的发生(虽说概率不大,但也不能不防啊),因此:使用LOCK进行并发编程显然不太适用。

    还好,微软一直在更新自己的东西:

       .NET Framework 4提供了新的线程安全和扩展的并发集合,它们能够解决潜在的死锁问题和竞争条件问题,因此在很多复杂的情形下它们能够使得并行代码更容易编写,这些集合尽可能减少使用锁的次数,从而使得在大部分情形下能够优化为最佳性能,不会产生不必要的同步开销。

    ConcurrentQueue 队列

       ConcurrentQueue 是完全无锁的,能够支持并发的添加元素,先进先出。下面贴代码,详解见注释:

    class Program
        {
            private static object o = new object();
            /*定义 Queue*/
            private static Queue<Product> _Products { get; set; }
            private static ConcurrentQueue<Product> _ConcurrenProducts { get; set; }
            /*  coder:天才卧龙  
             *  代码中 创建三个并发线程 来操作_Products 和 _ConcurrenProducts 集合,每次添加 10000 条数据 查看 一般队列Queue 和 多线程安全下的队列ConcurrentQueue 执行情况
             */
            static void Main(string[] args)
            {
                Thread.Sleep(1000);
                _Products = new Queue<Product>();
                Stopwatch swTask = new Stopwatch();//用于统计时间消耗的
                swTask.Start();
    
                /*创建任务 t1  t1 执行 数据集合添加操作*/
                Task t1 = Task.Factory.StartNew(() =>
                {
                    AddProducts();
                });
                /*创建任务 t2  t2 执行 数据集合添加操作*/
                Task t2 = Task.Factory.StartNew(() =>
                {
                    AddProducts();
                });
                /*创建任务 t3  t3 执行 数据集合添加操作*/
                Task t3 = Task.Factory.StartNew(() =>
                {
                    AddProducts();
                });
    
                Task.WaitAll(t1, t2, t3);
                swTask.Stop();
                Console.WriteLine("List<Product> 当前数据量为:" + _Products.Count);
                Console.WriteLine("List<Product> 执行时间为:" + swTask.ElapsedMilliseconds);
    
                Thread.Sleep(1000);
                _ConcurrenProducts = new ConcurrentQueue<Product>();
                Stopwatch swTask1 = new Stopwatch();
                swTask1.Start();
    
                /*创建任务 tk1  tk1 执行 数据集合添加操作*/
                Task tk1 = Task.Factory.StartNew(() =>
                {
                    AddConcurrenProducts();
                });
                /*创建任务 tk2  tk2 执行 数据集合添加操作*/
                Task tk2 = Task.Factory.StartNew(() =>
                {
                    AddConcurrenProducts();
                });
                /*创建任务 tk3  tk3 执行 数据集合添加操作*/
                Task tk3 = Task.Factory.StartNew(() =>
                {
                    AddConcurrenProducts();
                });
    
                Task.WaitAll(tk1, tk2, tk3);
                swTask1.Stop();
                Console.WriteLine("ConcurrentQueue<Product> 当前数据量为:" + _ConcurrenProducts.Count);
                Console.WriteLine("ConcurrentQueue<Product> 执行时间为:" + swTask1.ElapsedMilliseconds);
                Console.ReadLine();
            }
    
            /*执行集合数据添加操作*/
    
            /*执行集合数据添加操作*/
            static void AddProducts()
            {
                Parallel.For(0, 30000, (i) =>
                {
                    Product product = new Product();
                    product.Name = "name" + i;
                    product.Category = "Category" + i;
                    product.SellPrice = i;
                    lock (o)
                    {
                        _Products.Enqueue(product);
                    }
                });
    
            }
            /*执行集合数据添加操作*/
            static void AddConcurrenProducts()
            {
                Parallel.For(0, 30000, (i) =>
                {
                    Product product = new Product();
                    product.Name = "name" + i;
                    product.Category = "Category" + i;
                    product.SellPrice = i;
                    _ConcurrenProducts.Enqueue(product);
                });
    
            }
        }
    
        class Product
        {
            public string Name { get; set; }
            public string Category { get; set; }
            public int SellPrice { get; set; }
        }

    从执行时间上来看,使用 ConcurrentQueue 相比 LOCK 明显快了很多!

       1.BlockingCollection 与经典的阻塞队列数据结构类似,能够适用于多个任务添加和删除数据,提供阻塞和限界能力。

       2.ConcurrentBag 提供对象的线程安全的无序集合

       3.ConcurrentDictionary  提供可有多个线程同时访问的键值对的线程安全集合

       4.ConcurrentQueue   提供线程安全的先进先出集合

       5.ConcurrentStack   提供线程安全的后进先出集合

  • 相关阅读:
    BOI 2002 双调路径
    BOI'98 DAY 2 TASK 1 CONFERENCE CALL Dijkstra/Dijkstra+priority_queue/SPFA
    USACO 2013 November Contest, Silver Problem 2. Crowded Cows 单调队列
    BOI 2003 Problem. Spaceship
    USACO 2006 November Contest Problem. Road Blocks SPFA
    CEOI 2004 Trial session Problem. Journey DFS
    USACO 2015 January Contest, Silver Problem 2. Cow Routing Dijkstra
    LG P1233 木棍加工 动态规划,Dilworth
    LG P1020 导弹拦截 Dilworth
    USACO 2007 February Contest, Silver Problem 3. Silver Cow Party SPFA
  • 原文地址:https://www.cnblogs.com/netlock/p/14061160.html
Copyright © 2020-2023  润新知