C#多线程

一、什么是多线程

多线程是指在一个程序中同时运行多个线程,每个线程都是独立的执行流。多线程可以提高程序的执行效率,特别是在需要同时执行多个任务的情况下。多线程可以同时进行多个任务,而不需要等待某个任务的完成才能进行下一个任务。多线程可以在单个程序中同时处理多个任务,并且可以充分利用多核处理器的能力。

主线程与子线程

主线程通常是程序启动时自动创建并执行的第一个线程。它是程序的入口点,在程序开始时启动,并在程序结束时终止,是整个程序的起点和终点,还负责管理由它创建的子线程,包括创建、启动、挂起、停止等操作。

子线程则是由主线程创建的额外线程。子线程可以并行执行,独立于主线程,通常用于执行耗时的任务,以避免阻塞主线程。子线程的生命周期可以独立于主线程,可以在主线程运行期间创建和终止,子线程的阻塞不会影响主线程的执行。

主线程和子线程之间的交互通常通过线程同步机制来实现。

二、ThreadPool

提供了一个线程池,用于管理和执行等待队列中的任务。使用线程池可以避免频繁地创建和销毁线程,从而提高应用程序的性能和响应能力。

线程池的工作方式是将任务放入队列,然后在线程创建后启动这些任务。如果线程数量超过了最大数量,超出数量的线程会排队等候,等其他线程执行完毕,再从队列中取出任务来执行。

1、使用

在Main中(代表主线程)。

使用QueueUserWorkItem方法将任务提交到线程池当中。

using NewTask;

//将方法提交到线程池当中
ThreadPool.QueueUserWorkItem(NewPool.NewThreadPool,"你好");//第一个参数需要执行的方法,第二个参数为传参
//主线程的线程ID
Console.WriteLine("我是主线程" + "我的线程ID:" + Thread.CurrentThread.ManagedThreadId);
//等待输入,避免主线程消亡
Console.ReadLine();

创建一个NewPool类(代表子线程) 。

public class NewPool
{
    public static void NewThreadPool(Object message)
    {
        //接收传参输出
        Console.WriteLine(message.ToString());
        // 模拟耗时操作  
        Thread.Sleep(2000);
        //子线程的线程ID
        Console.WriteLine("我是子线程" + "我的线程ID:" + Thread.CurrentThread.ManagedThreadId);
    }
}

NewThreadPool方法作为线程池中的一个任务被提交,并且主线程继续执行其他任务。当任务在线程池中运行时,主线程不会等待它完成。

注意:当主线程被干掉,那么子线程大概率也活不下来。

2、线程池大小

线程池的大小不是直接设置的,而是由系统根据工作负载和可用资源动态管理的,但是我们可以控制线程数量的最小值和最大值。

ThreadPool.SetMinThreads(10, 0); // 设置最小工作线程数为10  
ThreadPool.SetMaxThreads(20, 20); // 设置最大工作线程数为20

进程的线程池的默认大小取决于虚拟地址空间的大小等各种因素,需求较低时,线程池线程的实际数量可能低于最小值。

ThreadPool的三个属性:

CompletedWorkItemCount 获取迄今为止已处理的工作项数。

PendingWorkItemCount 获取当前已加入处理队列的工作项数。

ThreadCount 获取当前存在的线程池线程数。

三、Task

Task实际上并不直接创建线程,而是利用线程池(ThreadPool)来执行工作,当你创建一个Task并调用时,它会被提交到线程池,线程池会负责安排一个线程来执行该任务,现在官方推荐使用Task。

1、创建任务

使用Task.Run来创建一个任务,提交到线程池处理。

Console.WriteLine("我是主线程ID:"+Thread.CurrentThread.ManagedThreadId);
//创建一个任务并交给线程池处理
Task task1 = Task.Run(() =>
{
    Console.WriteLine("我是task1子线程ID:" + Thread.CurrentThread.ManagedThreadId);
});

Task task2 = Task.Run(() =>
{
    Console.WriteLine("我是task2子线程ID:" + Thread.CurrentThread.ManagedThreadId);
});
//等待输入,避免主线程死亡
Console.ReadLine();

2、处理异常 

Task抛出的异常会存在AggregateException中。

try
{
    //创建任务
}
catch(AggregateException ae)
{
    foreach (var innerException in ae.InnerExceptions)
    {
        Console.WriteLine(innerException.Message);
    }
}

3、解决数据不一致

两个或多个线程在访问共享数据时(共同一个资源),由于它们的执行顺序不确定,导致最终结果依赖于线程的执行顺序,可能会导致数据不一致的问题。

创建一个Account类

public class Account
{
    private readonly object _lock = new object(); // 锁对象  
    private int _mone;//余额

    public Account(int @int)
    {
        _mone = @int;
    }
    //进行消费
    public void Withdraw(int amount)
    {
        lock (_lock) // 获取锁  
        {
            //判断余额
            if (_mone >= amount)
            {
                _mone -= amount;
                Console.WriteLine($"消费{amount}元,余额{_mone}元");
            }
            else
            {
                Console.WriteLine($"消费{amount}元,余额{_mone}元,余额不足");
            }
        } // 离开lock代码块释放锁
    }

    public int GetBalance()
    {
        // 这里不需要lock,因为只是读取数据,但如果有复合操作则仍需要锁保护  
        return _mone;
    }
}

在进入Withdraw方法中lock确保只能有一个线程能够修改余额,当线程进入到lock时会获取锁,如果此时已经有一个线程占用该锁,那么第一个线程就会阻塞,一直等到第二个线程释放锁,这样就只会有一个线程能够使用该方法,避免了数据不一致的问题。

在Main中

using NewTask;
//初始一千余额
Account account = new Account(1000);
//创建任务并交给线程池
Task task1 = Task.Run(() => account.Withdraw(1000));
Task task2 = Task.Run(() => account.Withdraw(50));
//等待两个线程完成
await Task.WhenAll(task1, task2);
//查看余额
Console.WriteLine(account.GetBalance());

4、任务等待

(1)、Task.WhenAll

用于等待一组任务全部完成,它接受一个Task对象数组或IEnumerable<Task> 集合作为参数,可以方便地并行执行多个任务,并等待它们全部完成。

Console.WriteLine("我是主线程ID:" + Thread.CurrentThread.ManagedThreadId);
//创建一个任务并交给线程池处理
Task task1 = Task.Run(() =>
{
    Console.WriteLine("我是task1子线程ID:" + Thread.CurrentThread.ManagedThreadId);
});    

Task task2 = Task.Run(() =>
{
    //模拟耗时操作
    Thread.Sleep(2000);
    Console.WriteLine("我是task2子线程ID:" + Thread.CurrentThread.ManagedThreadId);
    
});
//等待所有任务都完成再进行后续任务
await Task.WhenAll(task1,task2);

Console.WriteLine("我是下一步");

(2)、Task.WhenAny

用于等待一组任务中的任何一个完成,它接受一个Task对象数组或IEnumerable<Task> 集合作为参数,在需要并行执行多个任务,但只需要等待其中一个任务完成即可。

Console.WriteLine("我是主线程ID:" + Thread.CurrentThread.ManagedThreadId);
//创建一个任务并交给线程池处理
Task task1 = Task.Run(() =>
{
    Console.WriteLine("我是task1子线程ID:" + Thread.CurrentThread.ManagedThreadId);
});    

Task task2 = Task.Run(() =>
{
    //模拟耗时操作
    Thread.Sleep(2000);
    Console.WriteLine("我是task2子线程ID:" + Thread.CurrentThread.ManagedThreadId);
    
});
//等待其中一个任务完成就进行后续任务
await Task.WhenAny(task1,task2);

Console.WriteLine("我是下一步");
Console.ReadLine();

(3)、Wait

阻塞当前线程,直到任务完成执行后面任务,该方法可以传入一个int类型的参数,如:task2.Wait(1000),表示阻塞1000毫秒后执行后续任务,如果等待的线程500毫秒完成,那么会在501毫秒执行后续任务,如果等待的线程2000毫秒完成,那么会在1000毫秒执行后续任务。

Console.WriteLine("我是主线程ID:" + Thread.CurrentThread.ManagedThreadId);
//创建一个任务并交给线程池处理
Task task1 = Task.Run(() =>
{
    Console.WriteLine("我是task1子线程ID:" + Thread.CurrentThread.ManagedThreadId);
});    

Task task2 = Task.Run(() =>
{
    //模拟耗时操作
    Thread.Sleep(2000);
    Console.WriteLine("我是task2子线程ID:" + Thread.CurrentThread.ManagedThreadId);
    
});
//等待task2任务完成
task2.Wait();

Console.WriteLine("我是下一步");

 其他的一些方法:

Task.WaitAll与Task.WhenAll类似,但是Task.WhenAll是异步,而Task.WaitAll是同步。

Task.WaitAny与Task.WhenAny类似,但是Task.WhenAny是异步,而Task.WaitAny是同步。

5、取消任务

取消一个Task任务通常使用CancellationToken,创建一个CancellationTokenSource实例,生成一个CancellationToken,将该属性传递到任务当中,任务需要定期检查IsCancellationRequested属性,用于判断是否发送了取消请求,所以如果没有检查,那么任务会一直执行下去。

创建一个类Doless。

public class Doless
{
    public static void DoWork(CancellationToken token)
    {
        //判断任务是否取消
        while (!token.IsCancellationRequested)
        {
            Console.WriteLine("你好");
            Thread.Sleep(500); // 模拟耗时操作  
        }
    }
}

在Main中。

using NewTask;

using (CancellationTokenSource cts = new CancellationTokenSource())
{
    // 获取CancellationToken  
    CancellationToken token = cts.Token;

    // 启动Task并传递CancellationToken  
    Task task = Task.Run(() => Doless.DoWork(token), token);

    // 注册一个取消回调,当取消发生时执行 
    token.Register(() =>
    {
        Console.WriteLine("结束");
    });

    // 模拟一些工作,然后取消Task  
    Thread.Sleep(2000); // 等待一段时间  
    cts.Cancel(); // 发送取消请求  
    Console.ReadLine();
}

  • 13
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值