C#异步编程:多线程基础Thread类

Thrad类提供了:在不同线程上执行方法的能力

Thread类位于System.Threading名称空间下,学会使用一下几点技能,便可基本掌握最简单的多线程操作

知识点1:创建并启动线程

class Program
{
    static void Main(string[] args)
    {
        Thread thread1 = new Thread(new ThreadStart(CommonService.GetConnection));
        Console.WriteLine("thread1 is ready to running....");
        thread1.Start();

        Console.ReadLine();
    }
}

class CommonService
{
    public static void GetConnection()
    {
        Console.WriteLine("connect success....");
    }
}

说明:1、new一个Thread对象,便创建了一个新的线程;2、调用Start方法,将创建的线程的状态设置为running状态;操作系统会将该线程视为执行,然后寻找空闲时间,在该线程上执行GetConnection方法。

知识点2(不常用):暂停(挂起)某个线程一段时间

class Program
{
    static void Main(string[] args)
    {
        Thread.Sleep(new TimeSpan(0,0,5));//将当前线程暂停五秒(暂停的是,Main方法所在的主线程)
        Thread thread1 = new Thread(new ThreadStart(CommonService.GetConnection));
        Console.WriteLine("thread1 is ready to running....");
        thread1.Start();

        Console.ReadLine();
    }
}

class CommonService
{
    public static void GetConnection()
    {
        Thread.Sleep(TimeSpan.FromSeconds(3));//暂停的是GetConnection方法所在的线程
        Console.WriteLine("connect success....");
    }
}

说明:使用Thread类的静态方法--Sleep(),系统会自动判断执行的代码所处在哪一个线程"。1、在主线程中使用了Sleep,那么整个应用会停留在该位置"假死"到指定的时间,才继续向下执行;2、在子线程中使用Sleep,那么子线程会暂停操作,到指定时间才继续执行。开发工作中,不建议使用Sleep()方法。

知识点3(不常用):在一个线程中等待另个线程执行完成再继续

当子线程创建并启动后,主线程同一时刻也在运行;子线程何时执行完毕是不确定的;如果我们希望等待某个线程执行完后,才继续执行另一个线程的代码,就需要使用Join()方法。

class Program
{
    static void Main(string[] args)
    {
        Thread.Sleep(new TimeSpan(0,0,5));//将Main方法所在的主线程暂停五秒
        Thread thread1 = new Thread(new ThreadStart(CommonService.GetConnection));
        Console.WriteLine("thread1 is ready to running....");
        thread1.Start();
        thread1.Join();//join方法有两个重载,这里使用无参数形式的Join()方法来等待线程执行结束。
        Console.WriteLine("main is ready to completed ....");
        Console.ReadLine();
    }
}

class CommonService
{
    public static void GetConnection()
    {
        Thread.Sleep(TimeSpan.FromSeconds(3));//暂停的是GetConnection方法所在的线程
        Console.WriteLine("connect success....");
    }
}

说明:从执行结果可以看出,使用Join()方法后,线程之间便存在了一种同步关系--即:你先干完,我再干。

知识点4:前台线程与后台线程

class Program
{
    static void Main(string[] args)
    {
        Thread thread = new Thread(new ThreadStart(CommonService.GetConnection));
        /**
         * 可以通过解除下面注释来观察前台线程与后台线程的区别:
         * 未解除注释--Main方法所在的主线程率先退出;GetConnection方法所在的子线程是前台线程,应用程序会等待子线程中的代码执行完毕后才结束进程。
         * 解除注释--Main方法所在的主线程率先退出;GetConnection方法所在的子线程是后台线程,应用程序检测进程中没有正在执行的前台线程后,直接结束进程。
         */
        //thread.IsBackground = true;
        thread.Start();
    }
}

class CommonService
{
    public static void GetConnection()
    {
        Console.WriteLine($"当前正在执行方法所在的线程是否为后台线程:{Thread.CurrentThread.IsBackground}");
        Thread.Sleep(TimeSpan.FromSeconds(3));
        Console.WriteLine("connect success....");
    }
}

以控制台程序为例:1、手动创建的线程,默认也是一个"前台线程";3、如果指定IsBackground=true,那么线程被指定为后台线程;4、应用程序会等待所有的前台线程执行完毕后再结束工作(如果程序中只剩下后台线程的话,应用程序会直接结束工作)

知识点5:给线程传递参数

使用Thread对象的Start方法的重载形式:通过给Start()方法传递参数,达到给线程传递参数

错误内容:System.InvalidOperationException:“该线程是用不接受参数的 ThreadStart 委托创建的。ThreadStart不接受参数,所以需要一个能够接收参数的委托:ParameterizedThreadStart

方式1:使用ParameterizedThreadStart创建线程、并通过给Start()方法传递参数,来给线程传递参数

private static void TestThreadBase()
{
    Thread thread = new Thread(new ParameterizedThreadStart(CommonService.ConnectionWithTarget));
    thread.Start(12);
}
class CommonService
{
    public static void ConnectionWithTarget(object targetName)
    {
        Console.WriteLine($"线程 {targetName} 连接成功....");
        Thread.Sleep(TimeSpan.FromSeconds(5));
        Console.WriteLine($"线程 {targetName} 退出 ...");
    }
}
/*
线程 12 连接成功....
线程 12 退出 ...
*/

使用ParameterizedThreadStart委托的方式给线程传递参数,那么执行线程的方法的参数列表就需要和ParameterizedThreadStart委托的参数列表匹配--即:需要改方法定义一个Object类型的形参;假如我希望执行线程的方法的参数是Student类型的(如下),那么使用ParameterizedThreadStart委托,还能奏效么?
准备一个入参为Student类型的方法

class CommonService
{
    public static void ShowTargetInfo(Student student) 
    {
        Console.WriteLine($"{student.StudentName}的性别是{student.StudentSex},身高是{student.StudentHeight}");
    }
}
class Student
{
    public int StudentId { get; set; }
    public string StudentName { get; set; }
    public double StudentHeight { get; set; }
    public char StudentSex { get; set; }
}

尝试给ParameterizedThreadStart委托添加ShowTargetInfo方法

上面的报错告诉我们,ShowTargetInfo方法不匹配ParameterizedThreadStart委托,所以我们需要转换思路:使用Lambda表达式来实例化ParameterizedThreadStart委托;然后再在Lambda表达式中调用ShowTargetInfo方法,这样ShowTargetInfo也算是在该线程上运行了。

方式2:使用Lambda表达式实例化委托类型,然后在Lambda表达式中调用方法,达到给线程传递参数的目的

private static void TestThreadBase()
{
    Student student = new Student() { StudentId = 11, StudentName = "小哇", StudentSex = '男', StudentHeight = 170.5 };
    //方式1
    Thread thread1 = new Thread(new ThreadStart(()=> 
    {
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
        CommonService.ShowTargetInfo(student);
    }));
    thread1.Start();

    //方式2
    Thread thread2 = new Thread(new ParameterizedThreadStart(item=> 
    {
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
        CommonService.ShowTargetInfo(student);
    }));
    thread1.Start();

    //方式3
    Thread thread3 = new Thread(new ParameterizedThreadStart(item =>
    {
        var stu = item as Student;
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
        CommonService.ShowTargetInfo(stu);
    }));
    thread3.Start(student);

    //方式1 简写形式 Lambda表达式隐式创建了ThreadStart委托(也是我最常用的给线程传递参数的形式)
    Thread thread4 = new Thread(()=> 
    {
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
        CommonService.ShowTargetInfo(student);
    });
    thread4.Start();

    //方式2 简写形式 Lambda表达式隐式创建了ParameterizedThreadStart委托
    Thread thread5 = new Thread(item =>
    {
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
        CommonService.ShowTargetInfo(student);
    });
    thread5.Start();

    //方式3 简写形式 Lambda表达式隐式创建了ParameterizedThreadStart委托
    Thread thread6 = new Thread(item =>
    {
        var stu = item as Student;
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
        CommonService.ShowTargetInfo(stu);
    });
    thread5.Start(student);
}

向线程传递参数,本质上是向线程所执行的方法传入实参;利用Lambda表达式调用目标方法的形式,使目标方法参数列表的灵活度高,不受限制。

知识点6:在多线程中处理异常

当我们创建了一个线程并启动该线程,那么在该线程中一旦发生了异常,会发生什么后果呢?

在工作线程中(即:非主线程)发生了异常,会导致程序崩溃

private static void TestThreadException() 
{
    new Thread(()=> 
    {
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} is running .....");
        Thread.Sleep(3000);
        throw new Exception($"从线程{Thread.CurrentThread.ManagedThreadId}中抛出异常....");
    }).Start();
}
static void Main(string[] args)
{
    TestThreadException();
    Console.ReadLine();
}

分析:Lambda表达式中的代码片段是运行在子线程中的,三秒后该线程中抛出异常;由于没有处理该异常控制台程序直接崩溃。

在子线程的代码外部试图捕获异常也是徒劳的

static void Main(string[] args)
{
    try
    {
        TestThreadException();
    }
    catch (Exception ex)
    {
        Console.WriteLine("捕捉到了子线程的异常{0}", ex.Message);
    }
    Console.ReadLine();
} 

分析:本来我们以为在Main()方法中使用try..catch块儿来试图捕获子线程中的异常,但实际却并未能捕捉到该异常,程序照常崩溃

在子线程中处理异常,才是多线程中处理异常的正确方式

private static void TestThreadException()
{
    new Thread(() =>
    {
        try
        {
            Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} is running .....");
            Thread.Sleep(3000);
            throw new Exception($"从线程{Thread.CurrentThread.ManagedThreadId}中抛出异常....");
        }
        catch (Exception ex)
        {
            Console.WriteLine("捕捉到了子线程的异常{0}", ex.Message);
        }
    }).Start();
}
/*
3 is running .....
捕捉到了子线程的异常从线程3中抛出异常....
*/

多线程编程处理异常:不要在线程中抛出异常,而是使用try..catch块儿。

以上便是对多线程基础的知识点的总结,记录下来以便以后查阅。

posted @ 2020-12-27 15:37  BigBosscyb  阅读(583)  评论(0编辑  收藏  举报