前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C#学习笔记 线程操作

C#学习笔记 线程操作

作者头像
乐百川
发布2022-05-05 18:53:10
4500
发布2022-05-05 18:53:10
举报

完整代码在这里:https://github.com/techstay/csharp-learning-note

创建并使用线程

使用线程执行任务

要创建一个线程很简单,实例化一个System.Threading.Thread对象并向其构造函数传递一个无参无返回值的委托即可。创建完线程之后,线程并没有实际运行。要让其运行,需要调用其Start方法,这样会将其状态修改为就绪,可以随时被CPU调度运行。

代码语言:javascript
复制
public static void StartNewThread()
{
    Thread thread = new Thread(DoSomething);
    thread.Name = "线程1";
    PrintThreadInfo(thread);
    Console.WriteLine("线程大约3秒钟之后终止。");
    thread.Start();
    Console.WriteLine("线程结束之后按任意键继续。");
    Console.ReadKey();
}
private static void DoSomething()
{
    Thread.Sleep(3000);
    Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}已结束。");
}

线程带有很多属性,可以调用这些属性查看线程的状态。有些状态在线程停止之后会变为不可用的,试图调用不可用的属性会抛出异常。

代码语言:javascript
复制
public static void PrintThreadInfo(Thread thread)
{
    Console.WriteLine("显示线程信息:");
    Console.WriteLine("-------------------");
    Console.WriteLine($"当前线程名称:{thread.Name}");
    Console.WriteLine($"当前线程ID:{thread.ManagedThreadId}");
    Console.WriteLine($"是否是后台线程:{thread.IsBackground}");
    Console.WriteLine($"是否是线程池线程:{thread.IsThreadPoolThread}");
    Console.WriteLine($"当前线程优先级:{thread.Priority}");
    Console.WriteLine($"当前线程的状态:{thread.ThreadState}");
    Console.WriteLine("-------------------");
}

创建带有参数的线程

给线程传递的方法不仅可以是无参的,还可以带一个参数,用来执行一些需要更多信息的任务。要开出运行这样的线程,需要在其Start方法上传递线程要实际使用的参数:

代码语言:javascript
复制
public static void StartNewThreadWithArgument()
{
    Thread thread = new Thread(DoSomethingWithParameter);
    PrintThreadInfo(thread);
    Console.WriteLine("线程大约3秒钟之后终止。");
    thread.Start("一个字符串参数");
    Console.WriteLine("线程结束之后按任意键继续。");
    Console.ReadKey();
}
private static void DoSomethingWithParameter(object obj)
{
    Thread.Sleep(3000);
    Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}获取了{obj.GetType().Name}参数:{obj}。");
}

创建后台线程

线程可以分为前台线程和后台线程。只要应用程序中有一个前台线程还在运行,整个应用程序就不会停止。只有当所有前台线程终止的时候应用程序才会终止。后台线程则不同,只要前台线程都运行完毕,所有后台线程都会终止。

要创建后台线程,只需要将Thread类的IsBackground属性设为true即可。这样的话,线程就会变为后台线程。

代码语言:javascript
复制
public static void StartNewBackgroundThread()
{
    Thread thread = new Thread(DoSomething);
    thread.IsBackground = true;
    Console.WriteLine(Environment.NewLine + "线程会立即终止,因为它是后台线程:");
    PrintThreadInfo(thread);
    thread.Start();
}

线程的优先级

.NET线程本来是想设计为和Windows线程不同的线程模型,但是这个设计目标最后失败了。因此现在.NET线程就是Windows线程。Windows线程有32个优先级,从最低的0到最高的31。为了方便开发人员设定线程优先级,微软规定了6个进程优先级类和7个相对线程优先级。6个进程优先级类分别是Idle、Below Normal、Normal、Above Normal、High和Realtime。7个相对线程优先级类分别是Idle、Lowest、Below Normal、Normal、Above Normal,Highest和Time-Critical。一个线程的优先级是由它所在的进程优先级和相对线程优先级共同决定的。一般情况下,使用Normal级别的进程优先级和相对线程优先级就足够了。要修改线程的优先级,只需要修改Thread类中的Priority属性,向其传递ThreadPriority枚举中的值即可。

使用线程池线程

线程是一种宝贵的计算机资源,创建和销毁线程都需要进行大量工作。因此只有当进行单独长时间的计算任务或者指定一个线程进行专用任务的时候才需要自己创建线程。大多数时候,主需要使用线程池中的线程即可。

线程池是一个线程集合,里面包含了一定的线程,需要使用的时候可以向线程池申请线程,线程使用完毕之后不会被销毁,而是会回到线程池中一倍下次使用。因此使用线程池可以提高系统的效率。另外,线程池会根据系统的请求动态调整线程的数量,如果需要大量线程,线程池就会创建更多的线程;当系统闲置了一段时间以后,线程池就会销毁一些不用的线程。

要使用线程池,只需要向线程池类ThreadPool的静态方法QueueUserWorkItem传递WaitCallback委托和一个可选的state参数即可。其中WaitCallback委托是这样的:delegate void WaitCallback(object state);

代码语言:javascript
复制
public static void UseThreadPoolThreads()
{
    Random random = new Random();
    WaitCallback workWithoutParameter = (state) => Console.WriteLine($"显示随机数:{random.Next(1, 10)}");
    WaitCallback workWithOneParameter = (state) => Console.WriteLine($"显示参数:{state}");
    for (int i = 0; i < 5; ++i)
    {
        ThreadPool.QueueUserWorkItem(workWithoutParameter);
        ThreadPool.QueueUserWorkItem(workWithOneParameter, i);
    }
    //等待按键输入,防止程序退出(由于线程池都是后台线程)
    Console.ReadKey();
}

定时任务

可以使用System.Threading.Timer类来进行定时任务。该计时器会以给定的时间间隔执行任务,执行任务的时候会使用线程池线程。

初始化计时器的时候需要传递4个参数:第一个参数是一个TimerCallback类型的委托,这个委托接受一个object类型的状态参数,没有返回值,这个委托会在计时器满足条件的时候被调用;第二个参数是要传递给回调方法的状态参数,在不需要使用状态参数的时候可以为null;第三个参数dueTime是指计时器多长时间之后会启动,值为0的话立即启动,值为Timeout.Infinite的话永远不会启动;第四个参数period是指计时器经过多长时间再次调用回调方法,例如设为1000毫秒的话在计时器启动之后会每隔1000毫秒再次执行一次,如果值是Timeout.Infinite的话只会在根据dueTime的值执行一次,之后不会再执行。

在内部,线程池为所有的Timer对象分配一个线程,这个线程会在Timer对象满足条件的时候被唤醒,并将要执行的回调添加到线程池队列中。但是,如果回调方法比较费时,而计时器的间隔比较短,那么可能会有多个线程池线程同时执行。要防止这种情况,可以在初始化定时器的时候将period参数设为Timeout.Infinite,然后在回调方法中调用定时器的Change方法设置dueTime作为下一次执行的间隔,period参数仍然设为Timeout.Infinite。这样,就可以避免同时执行回调方法。

代码语言:javascript
复制
/// <summary>
/// 计数变量和计时器
/// </summary>
private static int number = 5;
private static Timer timer;
/// <summary>
/// 计数方法
/// </summary>
/// <param name="state"></param>
private static void Count(object state)
{
    if (number >= 0)
    {
        Console.Write(number--);
        timer.Change(1000, Timeout.Infinite);
    }

}
/// <summary>
/// 执行定时任务,计数器会每秒计数一次。
/// </summary>
public static void DoSomeRegularTasks()
{
    Console.WriteLine("开始执行定时任务:");
    timer = new Timer(Count, null, Timeout.Infinite, Timeout.Infinite);
    Console.WriteLine("开始计数,一秒一次,到0为止:");
    timer.Change(0, Timeout.Infinite);
    while (number >= 0)
    { }
    timer.Dispose();
    Console.WriteLine();
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2015-12-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 创建并使用线程
    • 使用线程执行任务
      • 创建带有参数的线程
        • 创建后台线程
          • 线程的优先级
          • 使用线程池线程
            • 定时任务
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档