• C#多线程


    前期知识:

    1.进程和线程是啥?

    进程:进程就是一个应用程序,对电脑的各种资源的占用

    线程:线程是程序执行的最小单位,任何操作都是线程完成的,线程依托进程存在的,一个进程可以有多个线程

    2.多线程
    为啥会出现多此线程?

    计算机的角度,因为CPU太快了,其他硬件跟不上CPU的速度。CPU可以分为时间片,大概就是时间分片---上下文切换(加载环境--计算--保存环境)。

    从微观角度上说,一个核一个时刻,只能执行一个线程;宏观上来说是多线程并发。

    另外CPU多核,可以独立工作。例如计算机是4核8线程中,核指的就是物理的核,线程指的是物理的核。

    3.C#语言的线程

    就是指Thread(.net 1.0的时候就出现了),Thread是一个类,是C#语言多线程对象的封装。

    4.同步和异步
    这个是指方法的描述。

    同步:当前行,当前方法执行完成后,再开始下一行,阻塞式的。

    异步:不会等待方法的完成,会直接进入下一行,非阻塞式的。

    异步多线程方法发块,因为每个线程并发运算;但是并不是线性增长。同步方法,时间长,资源占用少;异步方法时间快,CPU资源占用多,异步是资源换时间。多线程可能资源不够,还有管理成本。

    5.C#中的异步和多线程的区别?

    多线程就是多个Thread并发;

    异步是硬件式的异步;

    6.应用场景
    本身异步多线程就是顺序不可控的,当要控制顺序的时候,需要用到异步回调,3种方式可以实现

    注意:不要忘记本心,应用多线程的前提:多个独立的任务可以同时运行。

    7.委托的异步调用

    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 _303Thread
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                //不带返回值的委托异步调用
                {
                    Action action;
                    action = DoSomething;
                    //action.Invoke();//同步
                    IAsyncResult iAsyncResult = null;
                    AsyncCallback async = new AsyncCallback(ia =>
                    {
                        Console.WriteLine(object.ReferenceEquals(ia, iAsyncResult));
                        Console.WriteLine("所有任务都完成了");
                    });
                    iAsyncResult = action.BeginInvoke(async, "button1_Click");
                    //上面一行代码的含义:action.BeginInvoke开启一个异步线程,
                    //异步线程的执行完,产生一个IAsyncResult
                    //最后执行回调委托,async。
                }
    
                //带返回值的委托异步调用
                {
                    Func<int> func = () =>
                    {
                        Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                        Thread.Sleep(100);
                        return DateTime.Now.Day;
                    };
                    //int result = func.Invoke();
    
                    IAsyncResult asyncResult = func.BeginInvoke(ia =>
                    {
                        Console.WriteLine(ia.AsyncState);
                        Console.WriteLine(func.EndInvoke(ia));
                    }, "传入的参数");
                    //Console.WriteLine($"{func.EndInvoke(asyncResult)}");
                }
            }
            private void DoSomething()
            {
                for (int i = 0; i < 5; i++)
                {
                    Console.WriteLine($"*********DoSomething Start:{Thread.CurrentThread.ManagedThreadId}***********");
                    for (int j = 0; j < 100; j++)
                    {
                        Thread.Sleep(5);
                    }
                    Console.WriteLine($"*********DoSomething End:{Thread.CurrentThread.ManagedThreadId}***********");
                }
            }
        }
    }

     Task专辑

    .net framwork 3.0出现的。命名空间是System.Thread.Tasks。

    Task是基于ThreadPool封装的,之后的多线程主流都使用Task。

    什么时候用多线程?

    首先,任务得能够并发运行。任务可以分拆,且各不相干。

     1 using System;
     2 using System.Collections.Concurrent;
     3 using System.Collections.Generic;
     4 using System.Linq;
     5 using System.Text;
     6 using System.Threading;
     7 using System.Threading.Tasks;
     8 
     9 namespace _00_测试的例子
    10 {
    11     class Program
    12     {
    13         static void Main(string[] args)
    14         {
    15             List<Task> list = new List<Task>();
    16             Console.WriteLine($"项目经理建立项目,线程ID:{Thread.CurrentThread.ManagedThreadId}");
    17             list.Add(Task.Factory.StartNew(() => DoSomething("小明", "Client")));
    18             list.Add(Task.Factory.StartNew(() => DoSomething("小涨", "Portal")));
    19             list.Add(Task.Factory.StartNew(() => DoSomething("小王", "Service")));
    20 
    21             //Task的一个API的含义
    22             //Task.WaitAll(list.ToArray());//阻塞当前线程,等待所有任务完成后,才进入下一行,卡界面
    23             //Task.WaitAll(list.ToArray(), 1000);//限时等待1000ms,最多就等1000ms
    24             //Task.WaitAny(list.ToArray());//阻塞当前线程,等待某个任务完成后,进入下一行,卡界面
    25 
    26             Task.WhenAny(list.ToArray()).ContinueWith(t =>
    27             {
    28                 Console.WriteLine($"得意的笑,线程ID:{Thread.CurrentThread.ManagedThreadId}");
    29             });//等待某个任务完成后,不卡界面,非阻塞
    30             Task.WhenAll(list.ToArray()).ContinueWith(t =>
    31             {
    32                 Console.WriteLine($"部署环境,联调测试,线程ID:{Thread.CurrentThread.ManagedThreadId}");
    33             });//等待所有任务完成后,不卡界面,非阻塞
    34 
    35             //Console.WriteLine($"告诉甲方,项目可以验收了, 线程ID:{Thread.CurrentThread.ManagedThreadId}");
    36             Console.ReadKey();
    37         }
    38         /// <summary>
    39         /// 做事情
    40         /// </summary>
    41         /// <param name="str1"></param>
    42         /// <param name="str2"></param>
    43         public static void DoSomething(string str1, string str2)
    44         {
    45             Console.WriteLine($"我是{str1},Start:{str2},线程ID是:{Thread.CurrentThread.ManagedThreadId }");
    46             long result = 0;
    47             for (int i = 0; i < 100000000; i++)
    48             {
    49                 result += i;
    50             }
    51             Console.WriteLine($"我是{str1},End:{str2},线程ID是:{Thread.CurrentThread.ManagedThreadId }");
    52         }
    53 
    54     }
    55 }

     Task的一些API

    多线程中,Task的4个API熟练使用,就能应付工作中大部分多线程了。

    Task.WaitAny:阻塞当前线程,等待某个任务完成,进入下一行,会卡界面
    Task.WaitAll:阻塞当前线程,等待所有任务完成,进入下一行,会卡界面
    Task.WhenAny:等待某个任务完成后,不卡界面,非阻塞
     Task.WhenAll:等待所有任务完成后,不卡界面,非阻塞
    taskFactory.ContinueWhenAny和taskFactory.ContinueWhenAll和上面Task的API功能相同。
    Task.WhenAny(list.ToArray()).ContinueWith()和Task.WhenAll(list.ToArray()).ContinueWith()和上面的taskFactory的ContinueWhenAny()和ContinueWhenAll()功能是一样的。
     1 using System;
     2 using System.Collections.Generic;
     3 using System.ComponentModel;
     4 using System.Data;
     5 using System.Drawing;
     6 using System.Linq;
     7 using System.Text;
     8 using System.Threading;
     9 using System.Threading.Tasks;
    10 using System.Windows.Forms;
    11 
    12 namespace _00_测试的例子
    13 {
    14     public partial class Form1 : Form
    15     {
    16         public Form1()
    17         {
    18             InitializeComponent();
    19         }
    20 
    21         private void button1_Click(object sender, EventArgs e)
    22         {
    23             List<Task> list = new List<Task>();
    24             Console.WriteLine($"项目经理建立项目,线程ID:{Thread.CurrentThread.ManagedThreadId}");
    25             list.Add(Task.Factory.StartNew(() => DoSomething("小明", "Client")));
    26             list.Add(Task.Factory.StartNew(() => DoSomething("小涨", "Portal")));
    27             list.Add(Task.Factory.StartNew(() => DoSomething("小王", "Service")));
    28 
    29             //Task的一个API的含义
    30             //Task.WaitAny(list.ToArray());//阻塞当前线程,等待某个任务完成后,进入下一行,卡界面
    31             //Task.WaitAll(list.ToArray());//阻塞当前线程,等待所有任务完成后,才进入下一行,卡界面
    32             //Task.WaitAll(list.ToArray(), 1000);//限时等待1000ms,最多就等1000ms
    33 
    34             Task.WhenAny(list.ToArray()).ContinueWith(t =>
    35                    {
    36                        Console.WriteLine($"得意的笑,线程ID:{Thread.CurrentThread.ManagedThreadId}");
    37                    });//等待某个任务完成后,不卡界面,非阻塞
    38             Task.WhenAll(list.ToArray()).ContinueWith(t =>
    39               {
    40                   Console.WriteLine($"部署环境,联调测试,线程ID:{Thread.CurrentThread.ManagedThreadId}");
    41               });//等待所有任务完成后,不卡界面,非阻塞
    42 
    43             {//TaskFactory的两个API函数,跟上面Task的函数功能相同
    44                 //TaskFactory taskFactory = new TaskFactory();
    45                 //taskFactory.ContinueWhenAny(list.ToArray(), t =>
    46                 //{
    47                 //    Console.WriteLine($"得意的笑,线程ID:{Thread.CurrentThread.ManagedThreadId}");
    48                 //});
    49 
    50                 //taskFactory.ContinueWhenAll(list.ToArray(), tList =>
    51                 //{
    52                 //    Console.WriteLine($"部署环境,联调测试,线程ID:{Thread.CurrentThread.ManagedThreadId}");
    53                 //});
    54             }
    55 
    56             Console.WriteLine($"告诉甲方,项目可以验收了, 线程ID:{Thread.CurrentThread.ManagedThreadId}");
    57         }
    58         /// <summary>
    59         /// 做事情
    60         /// </summary>
    61         /// <param name="str1"></param>
    62         /// <param name="str2"></param>
    63         public static void DoSomething(string str1, string str2)
    64         {
    65             Console.WriteLine($"我是{str1},Start:{str2},线程ID是:{Thread.CurrentThread.ManagedThreadId }");
    66             long result = 0;
    67             for (int i = 0; i < 1000000000; i++)
    68             {
    69                 result += i;
    70             }
    71             Console.WriteLine($"我是{str1},End:{str2},线程ID是:{Thread.CurrentThread.ManagedThreadId }");
    72         }
    73     }
    74 }

    await和asycn,.net framwork4.5出现的语法糖

    1>通常是成对出现的,async是放到方法上的,任意个方法都可以使用。await是放到Task前面的。

    2>await和asycn要么不用,要么用到底。用到最后一层,一层层传到上层。

    3>任何一个方法前面都可以加上async,方法里面await可有,也可没有。await一定是出现在task之前

     之所以用,目的是想用同步的方式,写异步操作。

    下面一段代码,其中方法Show()是实现方式就是ShowNew()。await后面的那份部分代码,其实就是task.ContinueWith回调函数。

    注意:非UI线程中,await后面的动作都是子线程完成的;UI线程中,await后面的动作都是主线程完成的。

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading;
     6 using System.Threading.Tasks;
     7 
     8 namespace _00_测试的例子
     9 {
    10     class Program
    11     {
    12         static void Main(string[] args)
    13         {
    14             Console.WriteLine($"Main1:{Thread.CurrentThread.ManagedThreadId}");
    15             Task task = Show();
    16             Console.WriteLine($"Main2:{Thread.CurrentThread.ManagedThreadId}");
    17             Console.WriteLine("=====================================================");
    18             Console.WriteLine($"Main1==:{Thread.CurrentThread.ManagedThreadId}");
    19             Task task2 = ShowNew();
    20             Console.WriteLine($"Main2==:{Thread.CurrentThread.ManagedThreadId}");
    21             Console.ReadKey();
    22         }
    23         public async static Task Show()
    24         {
    25             Console.WriteLine($"Show1:{Thread.CurrentThread.ManagedThreadId}");
    26             Task task = Task.Factory.StartNew(() =>
    27             {
    28 
    29                 Console.WriteLine($"Task1:{Thread.CurrentThread.ManagedThreadId}");
    30                 Thread.Sleep(1000);
    31                 Console.WriteLine($"Task2:{Thread.CurrentThread.ManagedThreadId}");
    32             });
    33             await task;
    34             Console.WriteLine($"Show2:{Thread.CurrentThread.ManagedThreadId}");
    35 
    36             Task task1 = Task.Factory.StartNew(() =>
    37             {
    38                 Console.WriteLine($"Task3:{Thread.CurrentThread.ManagedThreadId}");
    39                 Thread.Sleep(2000);
    40                 Console.WriteLine($"Task4:{Thread.CurrentThread.ManagedThreadId}");
    41             });
    42             await task1;
    43             Console.WriteLine($"Show5:{Thread.CurrentThread.ManagedThreadId}");
    44         }
    45         public async static Task ShowNew()
    46         {
    47             Console.WriteLine($"Show1:{Thread.CurrentThread.ManagedThreadId}");
    48             Task task = Task.Factory.StartNew(() =>
    49             {
    50 
    51                 Console.WriteLine($"Task1:{Thread.CurrentThread.ManagedThreadId}");
    52                 Thread.Sleep(1000);
    53                 Console.WriteLine($"Task2:{Thread.CurrentThread.ManagedThreadId}");
    54             });
    55 
    56             Task task4 = task.ContinueWith((t) =>
    57               {
    58                   Console.WriteLine($"Show2:{Thread.CurrentThread.ManagedThreadId}");
    59 
    60                   Task task1 = Task.Factory.StartNew(() =>
    61                   {
    62                       Console.WriteLine($"Task3:{Thread.CurrentThread.ManagedThreadId}");
    63                       Thread.Sleep(2000);
    64                       Console.WriteLine($"Task4:{Thread.CurrentThread.ManagedThreadId}");
    65                   });
    66                   task1.ContinueWith(t1 =>
    67                   {
    68                       Console.WriteLine($"Show5:{Thread.CurrentThread.ManagedThreadId}");
    69                   });
    70               });
    71         }
    72     }
    73 }

    异常处理,线程取消,多线程临时变量

     1>异常处理

    子线程里面不允许出现异常,自己处理好异常

    2>线程取消

    使用场景,多线程并发,某个失败后,通知其他线程,都停了吧别做了。

    Task是外部无法终止的,因为线程是OS的资源,无法掌控什么时候取消。线程自身停止自己,访问一个公共变量,修改它,线程不断访问它。

    CancellationTokenSource去标志是否取消任务,Cancel取消,IsCancellationRequested是否已经取消。

    Token启动Task的时候传入,如果Cancel了,这个任务放弃启动,抛出一个异常。

    3>多线程临时变量

    下图中,i为什么等于5,k反而是0到4?

    答案:i最后执行了++,就变成了5。全程只有一个i值。k的话,for循环5次,就有5个k。

    异步和多线程的区别?

    异步:是对方法的描述,是一个目的或者说是目标。异步不会等待方法完成,会直接进入下一行,非阻塞。

    多线程:是多个Thread并发。是一种实现异步的方式。

    线程安全

     公共变量:都能访问的局部变量

    lock只能锁引用类型,占用这个引用链接

    安全队列ConcurrentQueue一个线程完成操作

     1 using System;
     2 using System.Collections.Concurrent;
     3 using System.Collections.Generic;
     4 using System.Linq;
     5 using System.Text;
     6 using System.Threading;
     7 using System.Threading.Tasks;
     8 
     9 namespace _00_测试的例子
    10 {
    11     class Program
    12     {
    13         static int iTotalCount = 0;//全局变量
    14         static List<int> list = new List<int>();
    15         private static readonly object oValue = new object();
    16         static void Main(string[] args)
    17         {
    18             Start0();
    19             Console.ReadKey();
    20         }
    21         public static void Start0()
    22         {
    23             TaskFactory taskFactory = new TaskFactory();
    24             List<Task> listTask = new List<Task>();
    25             int iTotal = 0;
    26             for (int i = 0; i < 10000; i++)
    27             {
    28                 int iValue = i;
    29                 listTask.Add(taskFactory.StartNew(() =>
    30                 {
    31                     //lock(oValue)
    32                     //{
    33                     iTotalCount += 1;
    34                     iTotal += 1;
    35                     list.Add(iValue);
    36                     //}
    37                 }));
    38             }
    39             Task.WaitAll(listTask.ToArray());
    40             Console.WriteLine($"{iTotalCount}");
    41             Console.WriteLine($"{iTotal}");
    42             Console.WriteLine($"{list.Count}");
    43             Console.ReadKey();
    44         }
    45     }
    46 }

    上段代码执行结果如下图:循环10000次,实际执行结果却不是10000.因为多个线程同时访问变量,造成了数据丢失

     

    开放上段代码中屏蔽的部分,如下图,

    iTotalCount 和iTotal 和list个数都是10000万次了。这个是Lock锁住了这三个变量,让同一时间,是有一个线程访问。Lock只能锁引用类型,锁的是引用链接。

     

  • 相关阅读:
    ASP+Access UTF-8 网页乱码问题解决办法
    使用PowerDesigner生成Access数据库
    crontab详解
    Pending Statistics
    Recovery with Incremental Backups
    ASP的Global.asa使用说明
    cocos基础教程(5)数据结构介绍之cocos2d::Map<K,V>
    cocos基础教程(5)数据结构介绍之cocos2d::Vector
    cocos基础教程(4)基础概念介绍
    cocos进阶教程(1)Lua调用自定义C++类和函数的最佳实践
  • 原文地址:https://www.cnblogs.com/dfcq/p/16102943.html
Copyright © 2020-2023  润新知