C#教程

关注公众号 jb51net

关闭
首页 > 软件编程 > C#教程 > C#多线程使用

C#多线程基本使用小结

作者:小威编程

C#多线程编程涉及Thread、Task、异步和Parallel等工具,Thread类用于创建独立线程,通过Priority属性设置优先级,而线程池管理线程的调度和重用,本文给大家介绍C#多线程基本使用小结,感兴趣的朋友跟随小编一起看看吧

线程是并发编程的基础概念之一。在现代应用程序中,我们通常需要执行多个任务并行处理,以提高性能。C# 提供了多种并发编程工具,如ThreadTask、异步编程和Parallel等。

Thread 类

Thread 类是最基本的线程实现方法。使用Thread类,我们可以创建并管理独立的线程来执行任务。

基本使用Thread 

创建一个新的实例对象,将一个方法直接给Thread类,并使用实例对象启动线程运行。

   static void Test() {
         Console.WriteLine("Test事件开始执行");
            Thread.Sleep(1000);
            Console.WriteLine("Test事件睡眠之后打印数据");
            Console.WriteLine("Test事件id: " + Thread.CurrentThread.ManagedThreadId);
        }
        static void Main(string[] args)
        {
            #region //主线程id
            Console.WriteLine("主线程id: " + Thread.CurrentThread.ManagedThreadId);
            #endregion
            #region //传递一个方法给线程,执行方法
            Thread t = new Thread(Test);
            t.Start();
            #endregion
}

线程数据传输

传递单个参数

Thread 提供了一个 Start(object parameter) 方法,可以在启动线程时传递一个参数。

 
        static int Downlod(object obj) {
            string str = obj as string;
            Console.WriteLine(str);
        }
     static void Main(string[] args)
        {
      Thread t1 = new Thread(Downlod);
           //这里传递的参数是字符串
            t1.Start("数据传递方法执行 ,数据传递方法id: "+Thread.CurrentThread.ManagedThreadId);
}

这种方式适用于需要传递单个参数的情况。

使用类的实例方法传递多个参数

先new一个类的实例,给实例字段赋值,调用类的实例,通过实例调用方法,构造函数接收参数,然后在线程中调用该实例的方法。

     static void Main(string[] args)
        {
// 使用 DownloadTool 实例化并传递数据
DownloadTool downloadTool = new DownloadTool("www.baidu.com", "这是下载链接哦");
Thread thread = new Thread(downloadTool.Download);
thread.Start();
}
public class DownloadTool
{
    private string url;
    private string message;
    public DownloadTool(string url, string message)
    {
        this.url = url;
        this.message = message;
    }
    public void Download()
    {
        Console.WriteLine("下载链接: " + url);
        Console.WriteLine("提示信息: " + message);
        Console.WriteLine("Download 线程 ID: " + Thread.CurrentThread.ManagedThreadId);
    }
}
 

thread 线程启动时,它会执行 downloadTool.Download() 方法,输出传递的数据。

线程优先级 

在 C# 中,可以使用 Thread.Priority 属性来设置线程的优先级。线程优先级决定了操作系统在多线程环境中调度线程的顺序,但并不保证高优先级的线程总是比低优先级的线程更早或更频繁地执行。

线程优先级级别

C# 提供了五个线程优先级级别,定义在 ThreadPriority 枚举中:

  internal class Program
    {
        static void A()
        {
            int i = 0;
            while (true)
            {
                i++;
                Console.WriteLine($"A 输出第{i}");
                Thread.Sleep(1000);
            }
        }
        static void B()
        {
            int i = 0;
            while (true)
            {
                i++;
                Console.WriteLine($"B 输出第{i}");
                Thread.Sleep(1000);
            }
        }
        static void Main(string[] args)
        {
            //在C#中,线程的优先级可以通过Thread.Priority属性来设置和获取。
//        Lowest: 线程的优先级是最低的。在系统中存在其他活动线程时,此优先级的线程很少得到执行。
//BelowNormal: 线程的优先级低于正常线程。
//Normal: 线程的优先级是普通的,这是线程的默认优先级。
//AboveNormal: 线程的优先级高于正常线程。
//Highest: 线程的优先级是最高的。此优先级的线程会尽量优先于其他所有优先级的线程执行。
            Thread a = new Thread(A);
            Thread b = new Thread(B);
            a.Priority = ThreadPriority.Highest;
            a.Start();
            b.Priority = ThreadPriority.Lowest;
            b.Start();
            Console.WriteLine("按任意键停止线程...");
            Console.ReadKey();
            a.Join();
            b.Join();
            Console.WriteLine("线程已停止");
        }
    }

注意事项

线程优先级的最佳实践

通过合理设置线程优先级,可以帮助操作系统更好地调度线程,以满足应用程序的需求。 但通常在 .NET 应用程序中,多数情况下使用默认的 Normal 优先级就足够了。

线程池

线程池 (ThreadPool) 是一种高效的管理和调度线程的方式。线程池自动管理线程的创建、重用和销毁,从而减少了手动创建和管理线程的开销。

为什么使用线程池

基本使用 ThreadPool.QueueUserWorkItem 方法将任务排入线程池队列。

using System;
using System.Threading;
class Program
{
    static void Main(string[] args)
    {
        // 将任务排入线程池
        ThreadPool.QueueUserWorkItem(DoWork, "任务 1");
        ThreadPool.QueueUserWorkItem(DoWork, "任务 2");
        Console.WriteLine("主线程完成");
        Thread.Sleep(3000); // 等待线程池中的任务完成
    }
    static void DoWork(object state)
    {
        string taskName = (string)state;
        Console.WriteLine($"{taskName} 开始执行 - 线程ID: {Thread.CurrentThread.ManagedThreadId}");
        Thread.Sleep(1000); // 模拟耗时操作
        Console.WriteLine($"{taskName} 执行完成 - 线程ID: {Thread.CurrentThread.ManagedThreadId}");
    }
}

运行结果QueueUserWorkItem自动分配线程来执行任务。

QueueUserWorkItem 方法将任务排入线程池。它接收一个委托(即方法)和一个可选的状态对象(传递给方法的数据)。

DoWork 方法接受一个参数 state,这是从 QueueUserWorkItem 传递的。

Thread.Sleep(3000) 确保主线程不会立即退出,使得线程池中的任务有机会完成。

使用带返回值的线程池任务

C# 中的 ThreadPool 通常不直接支持返回值。如果需要获得任务结果,可以使用 Task,因为 Task 本质上也是线程池的一部分。Task 更适合于带返回值的异步操作。这里使用 Task.Run 来代替 ThreadPool

     static void Main(string[] args)
        {
  //使用tesk多线程
           int a= Task.Run(() =>
            {
              int a =  Dowload();
                return a;
            }).Result;
            Task<int> task = Task<int>.Run(()=>{
             int a =   Dowload();
                return a;
            });
            //初始化一个CancellationTokenSource实例
            CancellationTokenSource source = new CancellationTokenSource();
            //task.Start();
            task.Wait(1000);
            source.Cancel();
        int result  =    task.Result;
            Console.WriteLine(result);
            Console.WriteLine($"tesk返回值{a}");
}
        static int  Dowload() {
            int a = 0;
            for (int i = 0; i < 10; i++)
            {
              a=  a + i + 1;
            }
         int?  id= Task.CurrentId;
            Console.WriteLine("Current thread ID: " + id);
            return a;
        }

线程池的限制

线程池总结

线程池是一种高效的并发处理方式,适合于大多数轻量级的后台任务。在现代 C# 编程中,建议使用 Taskasync/await 进行异步操作,因为它们能简化代码,并且使用底层的线程池来管理线程。如果需要精确控制线程的执行,通常建议使用手动管理的 Thread 等。 

线程锁

使用多线程,在多线程编程中,如果多个线程同时访问和修改共享资源(如全局变量、文件、数据库等),可能会导致数据不一致或竞争条件。为了避免这种情况,多线程锁通过控制对共享资源的访问来保证线程安全性。

资源冲突示例:

  static void Main(string[] args)
        {
             //调用方法循环创建新的线程来执行方法
            StateObject state = new StateObject();
            for (int i = 0; i < 30; i++)
            {
                Thread thread = new Thread(state.ChangState);
                thread.Start();
            }
            Console.ReadKey();
        }
//一个StateObject类
        public class StateObject
        {
            private int state = 5;
//里面有个状态改变方法,当状态等于5的时候进入到方法中,然后state+1 打印的应该是6 和线程id
            public void ChangState()
            {
                if (state == 5)
                {
                    state++;
                    Console.WriteLine($"state:{state}  线程id:" + Thread.CurrentThread.ManagedThreadId);
                }
                state = 5;
            }
        }

 运行结果:

因为资源冲突的原因,有一些线程执行的时候,可能另外一个线程没有执行完,另外一个线程就已经进入到方法里面了。因为有修改的操作导致State状态混乱,资源冲突。这时候我们就需要一个东西来维护,让线程执行指定方法时一个一个的执行,不会冲突。

简单使用lock锁

1.创建一个private readonly object lockObject = new object(); // 锁对象

2.使用lock (lockObject){

//业务代码,执行到有修改的操作的代码

} // 使用锁对象来确保线程安全

        static void Main(string[] args)
        {
            StateObject state = new StateObject();
            for (int i = 0; i < 100; i++)
            {
                Thread thread = new Thread(state.ChangState);
                thread.Start();
            }
            Console.ReadKey();
        }
        public class StateObject
        {
            private object _lock = new object();
            private int state = 5;
            public void ChangState()
            {
                //使用锁保证每次执行方法的都是一个线程,防止资源冲突
                lock (_lock)
                {
                    if (state == 5)
                    {
                        state++;
                        Console.WriteLine($"state:{state}  线程id:" + Thread.CurrentThread.ManagedThreadId);
                    }
                    state = 5;
                }
            }
        }

 这样运行起来就能把每个线程操作隔离开,保证state的状态不会冲突。

为什么要创建一个 object 对象作为锁?

常见问题死锁:

锁并不是万能,也不是有锁就是最好,要看情况使用,锁也会产生问题,常见的问题就是死锁等问题。

演示死锁:

thread1方法1 的锁里面嵌套锁住了thread2的锁,thread2方法2的锁里面嵌套锁住了thread1的锁,这种锁与锁嵌套使用,就是容易出问题。导致线程锁死程序无法动弹。

   static void Main(string[] args)
        {
            DeadlockExample deadlockExample = new DeadlockExample();
            Thread t1 = new Thread(deadlockExample.Thread1);
            Thread t2 = new Thread(deadlockExample.Thread2);
            t1.Start();
            t2.Start();
            t1.Join();
            t2.Join();
            Console.ReadKey();
        }
    }
   public  class DeadlockExample
    {
        private static object lock1 = new object();
        private static object lock2 = new object();
        public  void Thread1()
        {
            lock (lock1)
            {
                Console.WriteLine("线程1:已获取锁1,正在等待锁2。。。");
                Thread.Sleep(100);  // 模拟某些工作
                lock (lock2)
                {
                    Console.WriteLine("线程1:获得锁2");
                }
            }
        }
        public  void Thread2()
        {
            lock (lock2)
            {
                Console.WriteLine("线程2:已获取锁2,正在等待锁1。。。");
                Thread.Sleep(100);  // 模拟某些工作
                lock (lock1)
                {
                    Console.WriteLine("线程2:获得锁1");
                }
            }
        }
    }

运行结果:

死锁发生在两个或多个线程相互等待对方持有的资源,导致所有线程都无法继续执行。

总结:

多线程锁在 C# 中主要用于解决以下问题:

C# 提供了多种锁机制,开发者可以根据应用场景选择合适的锁类型。

如果不想使用 lock 关键字,C# 还提供了其他锁机制,比如 MutexSemaphoreMonitor

到此这篇关于C#多线程基本使用和探讨的文章就介绍到这了,更多相关C#多线程使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文