zoukankan      html  css  js  c++  java
  • C#多线程和异步(一)——基本概念和使用方法


    一、多线程相关的基本概念

    进程(Process):是系统中的一个基本概念。 一个正在运行的应用程序在操作系统中被视为一个进程,包含着一个运行程序所需要的资源,进程可以包括一个或多个线程 。进程之间是相对独立的,一个进程无法访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为一个程序的基本边界。

    线程(Thread):是 进程中的基本执行单元,是操作系统分配CPU时间的基本单位 ,在进程入口执行的第一个线程被视为这个进程的 主线程 。

    多线程能实现的基础:

      1、CPU运行速度太快,硬件处理速度跟不上,所以操作系统进行分时间片管理。这样,宏观角度来说是多线程并发 ,看起来是同一时刻执行了不同的操作。但是从微观角度来讲,同一时刻只能有一个线程在处理。

      2、目前电脑都是多核多CPU的,一个CPU在同一时刻只能运行一个线程,但是 多个CPU在同一时刻就可以运行多个线程 。

    多线程的优点:

      可以同时完成多个任务;可以让占用大量处理时间的任务或当前没有进行处理的任务定期将处理时间让给别的任务;可以随时停止任务;可以设置每个任务的优先级以优化程序性能。

    多线程的缺点:

      1、 内存占用  线程也是程序,所以线程需要占用内存,线程越多,占用内存也越多(每个线程都需要开辟堆栈空间,多线程时有时需要切换时间片)。

      2、 管理协调 多线程需要协调和管理,所以需要占用CPU时间以便跟踪线程,线程太多会导致控制太复杂。

      3、 资源共享   线程之间对共享资源的访问会相互影响,必须解决争用共享资源的问题。

    二、C#中的线程使用

    2.1  基本使用

    2.1.1  无参时

    复制代码
     1      class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             ThreadTest test = new ThreadTest();
     6             //无参调用实例方法
     7             Thread thread1 = new Thread(test.Func2);
     8             thread1.Start();
     9             Console.ReadKey();
    10         }
    11     }
    12 
    13     class ThreadTest
    14     {
    15         public void Func2()
    16         {
    17             Console.WriteLine("这是实例方法");
    18         }
    19     }
    复制代码

    2.1.2  有参数时

    复制代码
     1      class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             ThreadTest test = new ThreadTest();
     6             //有参调用实例方法,ParameterizedThreadStart是一个委托,input为object,返回值为void
     7             Thread thread1 = new Thread(new ParameterizedThreadStart(test.Func1));
     8             thread1.Start("有参的实例方法");
     9             Console.ReadKey();
    10         }
    11     }
    12     class ThreadTest
    13     {
    14         public void Func1(object o)
    15         {
    16             Console.WriteLine(o);
    17         }
    18     }
    复制代码

    2.2  常用的属性和方法

    属性名称说明
    CurrentThread 获取当前正在运行的线程。
    ExecutionContext 获取一个 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。
    IsBackground bool,指示某个线程是否为后台线程。
    IsThreadPoolThread bool,指示线程是否属于托管线程池。
    ManagedThreadId int,获取当前托管线程的唯一标识符。
    Name string,获取或设置线程的名称。
    Priority

    获取或设置一个值,该值指示线程的调度优先级 。

    Lowest<BelowNormal<Normal<AboveNormal<Highest

    ThreadState

    获取一个值,该值包含当前线程的状态。

    Unstarted、Sleeping、Running 等

    方法名称说明
    GetDomain() 返回当前线程正在其中运行的当前域。
    GetDomainId() 返回当前线程正在其中运行的当前域Id。
    Start()   执行本线程。(不一定立即执行,只是标记为可以执行)
    Suspend() 挂起当前线程,如果当前线程已属于挂起状态则此不起作用
    Resume() 继续运行已挂起的线程。
    Interrupt() 中断处于 WaitSleepJoin 线程状态的线程。
    Abort() 终结线程
    Join() 阻塞调用线程,直到某个线程终止。
    Sleep()   把正在运行的线程挂起一段时间。

     看一个简单的演示线程方法的栗子:

     View Code

       当点击Start按钮,线程启动文本框会开始追加【第x次】字符串;点击Suspend按钮,线程挂起,停止追加字符串;点击Resume按钮会让挂起线程继续运行;点击Interrupt按钮弹出一个异常信息,线程状态从WaitSleepJoin变为Running,线程继续运行;点击Abort按钮会弹出一个异常信息并销毁线程。

    一点补充:Suspend、Resume方法已不建议使用,推荐使用AutoResetEvent和ManualResetEvent来控制线程的暂停和继续,用法也十分简单,这里不详细介绍,有兴趣的小伙伴可以研究下。

    2.3  线程同步

      所谓同步: 是指在某一时刻只有一个线程可以访问变量 。
      c#为同步访问变量提供了一个非常简单的方式,即使用c#语言的关键字Lock,它可以把一段代码定义为互斥段,互斥段在一个时刻内只允许一个线程进入执行,实际上是Monitor.Enter(obj),Monitor.Exit(obj)的语法糖。在c#中,lock的用法如下:

     lock (obj) { dosomething... }

    obj代表你希望锁定的对象,注意一下几点:

      1. lock不能锁定空值 ,因为Null是不需要被释放的。 2. 不能锁定string类型 ,虽然它也是引用类型的。因为字符串类型被CLR“暂留”,这意味着整个程序中任何给定字符串都只有一个实例,具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。 3. 值类型不能被lock ,每次装箱后的对象都不一样 ,锁定时会报错 4  避免锁定public类型 如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。

             推荐使用 private static readonly类型的对象,readonly是为了避免lock的代码块中修改对象,造成对象改变后锁失效。

    以书店卖书为例

    复制代码
     1      class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             BookShop book = new BookShop();
     6             //创建两个线程同时访问Sale方法
     7             Thread t1 = new Thread(book.Sale);
     8             Thread t2 = new Thread(book.Sale);
     9             //启动线程
    10             t1.Start();
    11             t2.Start();
    12             Console.ReadKey();
    13         }
    14     }
    15     class BookShop
    16     {
    17         //剩余图书数量
    18         public int num = 1;
    19         private static readonly object locker = new object();
    20         public void Sale()
    21         {
    22 
    23             lock (locker)
    24             {
    25                 int tmp = num;
    26                 if (tmp > 0)//判断是否有书,如果有就可以卖
    27                 {
    28                     Thread.Sleep(1000);
    29                     num -= 1;
    30                     Console.WriteLine("售出一本图书,还剩余{0}本", num);
    31                 }
    32                 else
    33                 {
    34                     Console.WriteLine("没有了");
    35                 }
    36             }
    37         }
    38     }
    复制代码

     代码执行结果时:

    如果不添加lock则执行的结果时:

    2.4  跨线程访问

    例子:点击测试按钮,给文本框赋值

    代码如下:

    复制代码
     1     private void myBtn_Click(object sender, EventArgs e)
     2         {
     3             Thread thread1 = new Thread(SetValue);
     4             thread1.Start();
     5 
     6         }
     7         private void SetValue()
     8         {
     9             for (int i = 0; i < 10000; i++)
    10             {
    11                 this.myTxtBox.Text = i.ToString();
    12             }
    13         }
    复制代码

     执行代码会出现如下错误:

    出现该错误的原因是:myTxtBox是由主线程创建的,thread1线程是另外一个线程,在.NET上执行的是托管代码, C#强制要求这些代码必须是线程安全的,即不允许跨线程访问Windows窗体的控件 

     解决的方法:

    复制代码
            public Form1()
            {
                InitializeComponent();
            }
            //点击按钮开启一个新线程
            private void myBtn_Click(object sender, EventArgs e)
            {
                Thread thread1 = new Thread(SetValues);
                thread1.IsBackground = true;
                thread1.Start();
            }
    
            //新线程给文本框赋值
            private void SetValues()
            {
                Action<int> setVal = (i) => { this.myTxtBox.Text = i.ToString(); };
                for (int i = 0; i < 10000; i++)
                {
                    this.myTxtBox.Invoke(setVal, i);
                }
            }
    复制代码

      Invoke:在“拥有控件的基础窗口句柄的线程”  即在本例的主线程上执行委托,这样就不存在跨线程访问了 ,因此还是线程安全的。

    参考文章:

    [1] https://www.cnblogs.com/dotnet261010/p/6159984.html

    [2] https://www.cnblogs.com/wwj1992/p/5976096.html

             
    欢迎转载,但请标明出处。如果本文对您有些许帮助,点击一下推荐吧,Thanks♪(・ω・)ノ
  • 相关阅读:
    eclipse乱码解决方法
    撞库攻击:一场需要用户参与的持久战
    网管把握市场需求,其实一点都不可怜 转载于 [http://tonyxiaohome.blog.51cto.com/925273/955589]
    mysql主从不同步,提示更新找不到记录
    安装完MongoDB后尝试mongod -dbpath命令为什么会一直卡在连接端口?
    mysqlslap对mysql进行压力测试
    mysqlslap: Error when connecting to server: 2001 Can't create UNIX socket (24) 解决方法
    MySQL架构
    VMWare linux 打印太多,看不到之前的记录的解决方法总结
    启动Mysql时,提示error 2002 的解决办法
  • 原文地址:https://www.cnblogs.com/Alex80/p/13407381.html
Copyright © 2011-2022 走看看