C#中的线程

进程/线程

  • 进程: 记录一个应用程序在运行的过程中,消耗的计算机资源的总记录; 计算机的资源:CPU/磁盘IO/网络
  • 线程:在程序运行的过程中,对于任何一条指令执行的一个最小执行流;
  • 进程和线程:一个进程至少要包含一个线程;
  • 多线程:程序在运行的过程中,可以多个线程同时去执行;
  • 重点: 进程,还是线程----都属于是计算机的资源;

启动线程的多种方式

直接New

Task task = new Task(() =>
{
    Console.WriteLine($"Start Thread -- {Thread.CurrentThread.ManagedThreadId.ToString("000")}");
});

Task.Run

Task.Run(() =>
{
    Console.WriteLine($"Start Thread -- {Thread.CurrentThread.ManagedThreadId.ToString("000")}");
});

TaskFactory

TaskFactory taskFactory = new TaskFactory();
taskFactory.StartNew(() =>
{
    Console.WriteLine($"Start Thread -- {Thread.CurrentThread.ManagedThreadId.ToString("000")}");
});

Task.Factory.StartNew

Task.Factory.StartNew(() =>
{
    Console.WriteLine($"Start Thread -- {Thread.CurrentThread.ManagedThreadId.ToString("000")}");
});

线程等待

Thread.Sleep 和 Task.Delay的区别

  1. Thread.Sleep:

等待主线程 直接卡顿界面 影响用户的体验

Thread.Sleep(1000);
  1. Task.Delay:

不会等待主线程 一般伴随 ContinueWith 一起使用

表示等待多长时间之后 可以执行一个回调方法

Task.Delay(1000);
=================
Task.Delay(1000).ContinueWith(task =>
{

});

Task.WaitAll

使用场景:

如果希望所有的线程都执行结束后,再执行一段业务逻辑

特点:

等待(等待主线程/UI线程)所有线程执行完 才执行以下业务 会卡顿界面

Task.WaitAll(tasks.ToArray()); 

Task.WaitAny

使用场景:

如果希望这一堆任务中,其中只要有一个任务执行完毕就继续往后执行

特点:

等待(等待主线程/UI线程)任一一个线程执行完 才执行以下业务 会卡顿界面

Task.WaitAny(tasks.ToArray());

Task.Factory.ContinueWhenAll

使用场景:

既等待也不卡界面的等待方式 等待一堆线程执行完之后 继续往下执行

特点:

等待一堆线程执行完之后 继续往下执行

第二个参数是执行完的Task集合

Task.Factory.ContinueWhenAll(tasks.ToArray(), taskList =>
{
    Console.WriteLine("不卡顿界面所有线程执行之后继续往后执行!!!");
});

Task.Factory.ContinueWhenAny

使用场景:

既等待也不卡界面的等待方式 等待一堆线程中,任何一个线程执行完之后 继续往下执行

特点:

等待一堆线程中,任何一个线程执行完之后 继续往下执行

第二个参数是执行完的Task

Task.AsyncState:执行完的状态对象,可以在创建的时候传值

Task.Factory.ContinueWhenAny(tasks.ToArray(), task =>
{
    Console.WriteLine($"{task.AsyncState}执行结束:不卡顿界面任何一个线程执行完之后继续往后执行!!!");
});

Task.AsyncState传值Demo:

必须使用:Task.Factory.StartNew,创建一个带有Object的Action对象

List<Task> tasks = new List<Task>();
for (int i = 0; i < 10; i++)
{
    int k = i;
    tasks.Add(Task.Factory.StartNew(o =>
    {
        Console.WriteLine($"开始逻辑:{k}");
        Thread.Sleep(200);
        Console.WriteLine($"结束逻辑:{k}");
    }, $"任务{k}"));
}

执行如下:

多线程异常处理

  • 在多线程的内部发生的异常 外部的 try-catch 是捕捉不到的
  • 如果把 try-catch 写在task内部可以捕捉到,不过是单个线程的捕捉
  • 捕捉异常:线程等待 Task.WaitAll
  • 捕捉到的多线程异常,有什么特点:AggregateException:类型的异常
  • 一个 try 可以对应多个 catch,异常类型的匹配优先具体类型,如果没有再匹配抽象

Demo代码:

try
{
    List<Task> taskList = new List<Task>();
    for (int i = 0; i < 20; i++)
    {
        int k = i;
        taskList.Add(Task.Run(() =>
        {
            if (k == 6)
                throw new Exception("k == 6");
            if (k == 9)
                throw new Exception("k == 9");
            if (k == 11)
                throw new Exception("k == 11");
        }));
    }
    Task.WaitAll(taskList.ToArray());
}
// 如果多个catch 优先匹配具体类型 如果没有才会去匹配抽象
catch (AggregateException ex)
{
    foreach (var item in ex.InnerExceptions)
    {
        Console.WriteLine(item.Message);
    }
}
catch (Exception ex)
{
    Console.WriteLine(ex.ToString());
}

线程取消

  • 线程不能从外部取消,线程只能自己取消自己
  • 来一个信号量:每个线程去判断这个信号量,如果有线程发生异常了,就去修改这个信号量
  • 线程取消的标准方案:CancellationTokenSource,提供IsCancellationRequested属性,默认值为false
  • 同时提供Cancel方法, IsCancellationRequested: false --> true

Demo代码:

CancellationTokenSource cts = new CancellationTokenSource();
List<Task> taskList = new List<Task>();
for (int i = 0; i < 200; i++)
{
    int k = i;
    taskList.Add(Task.Run(() =>
    {
        // 线程取消 只能取消还没有判断的线程
        if (cts.IsCancellationRequested)
        {
            Console.WriteLine($"开始:{Thread.CurrentThread.ManagedThreadId.ToString("000")}");
        }
        else
        {
            Console.WriteLine($"Cancel结束:{Thread.CurrentThread.ManagedThreadId.ToString("000")}");
            cts.Cancel();
        }
        if (k == 6)
            cts.Cancel();
    },cts.Token));
}

临时变量

  • 多线程:延时启动不会阻塞主线程
  • 因为task启动的时候需要请求计算机需要内存然后去启动,这个时候for循环如果很少的话,就会执行完毕,导致i为最大的++
  • 如果需要一个中间变量去接受i,然后输出,必须在循环里面:因为每次循环都是新的变量,如果把变量定义在for循坏外面则和输出i一样。
for (int i = 0; i < 20; i++)
{
    int k = i;
    Task.Run(() =>
     {
         Console.WriteLine(k);
     });
}

线程安全

多线程和单线程执行逻辑一致就是线程安全

  1. 加锁:标准锁:锁静态对象
    1. private static readonly object obj_lock = new object();
  1. 使用线程安全集合:
    1. ConcurrentDictionary<>
  1. 分片执行:
    1. 出现线程安全的主要场景,多个线程同时去操作变量、同时操作计算机文件
    2. 避开让多个线程有机会同时去操作同一块数据

加锁案例:

private static readonly object obj_lock = new object();
    List<Task> taskList = new List<Task>();
    int AsyncNum = 0;
    for (int i = 0; i < 1000_0; i++)
    {
        taskList.Add(Task.Run(() =>
        {
            lock (obj_lock)
            {
                AsyncNum++;
            }
        }));
    }
    Task.WaitAll(taskList.ToArray());
    Console.WriteLine(AsyncNum); 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值