C# 多线程

参考

环境

软件/系统 版本 说明
Windows windows 10 专业版 22H2 64 位操作系统, 基于 x64 的处理器
Microsoft Visual Studio Community 2022 (64 位) - Current 版本 17.13.6
.NET 6.0

正文

本文使用 DeepSeek 进行了相关内容的总结

完整示例代码

  1. Program.cs
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Threading.Tasks;
    
    namespace ConsoleApp4
    {
    	public class Program
    	{
    		/// <summary>
    		/// 
    		/// </summary>
    		/// <param name="args"></param>
    		static void Main(string[] args)
    		{
    			//TestThisThread();
    			//TestThreads();
    			//TestThreadPool();
    			//TestThreadPool1();
    			//TestTask();
    			//TestParallel();
    			TestBackgroundWorker();
    		}
    
    		/// <summary>
    		/// 
    		/// </summary>
    		/// <param name="args"></param>
    		//static async Task Main(string[] args)
    		//{
    		//    await TestTask1();
    		//}
    
    		/// <summary>
    		/// 获取当前主线程的状态 
    		/// https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.thread?view=net-6.0
    		/// </summary>
    		public static void TestThisThread()
    		{
    			// 以下属性均可以设置
    			Thread.CurrentThread.Name = "MainThread";
    			Console.WriteLine(Thread.CurrentThread.Name);
    			Console.WriteLine(Thread.CurrentThread.CurrentCulture);
    			Console.WriteLine(Thread.CurrentThread.IsAlive);
    			Console.WriteLine(Thread.CurrentThread.IsBackground);
    			Console.WriteLine(Thread.CurrentThread.IsThreadPoolThread);
    			Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    			Console.WriteLine(Thread.CurrentThread.Priority);
    			Console.WriteLine(Thread.CurrentThread.ThreadState);
    		}
    
    		/// <summary>
    		/// 手动多个线程
    		/// https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.thread?view=net-6.0
    		/// </summary>
    		public static void TestThreads()
    		{
    			List<Thread> threads = new List<Thread>();
    			for (int i = 0; i < 10; i++)
    			{
    				Thread thread = new Thread(() =>
    				{
    					Thread.CurrentThread.Name = "Thread"+i;
    					Thread.Sleep(1000);
    					Console.WriteLine(Thread.CurrentThread.Name);
    				});
    				threads.Add(thread);
    			}
    			threads.ForEach(item => item.Start());
    		}
    
    		/// <summary>
    		/// 线程池 ThreadPool
    		/// 主线程将在方法在线程池线程上运行之前退出。 线程池使用后台线程,如果所有前台线程都终止,后台线程不会使应用程序保持运行。 (这是一个争用条件的简单示例。) 
    		/// https://www.cnblogs.com/DonetRen/p/10167095.html
    		/// </summary>
    		public static void TestThreadPool()
    		{
    			//设置正在等待线程的事件为终止
    			//AutoResetEvent autoEvent = new AutoResetEvent(false);
    			//获取处于活动状态的线程池请求的数目
    			ThreadPool.GetMaxThreads(out int workerThreads, out int portThreads);
    			//设置辅助线程的最大数
    			workerThreads = 10;
    			//设置线程池中异步I/O线程的最大数
    			portThreads = 500;
    			//设置处于活动状态的线程池请求的数目
    			ThreadPool.SetMaxThreads(workerThreads, portThreads);
    			//执行线程池
    			//ThreadPool.QueueUserWorkItem(new WaitCallback((object parame) =>
    			//{
    			//    AutoResetEvent? stateInfo = parame as AutoResetEvent;
    			//    Thread.Sleep(1000);
    			//    Console.WriteLine(stateInfo?.ToString());
    			//}), autoEvent);
    			for (int i = 0; i < 10; i++)
    			{
    				ThreadPool.QueueUserWorkItem(new WaitCallback((object? index) =>
    				{
    					Thread.Sleep(1000);
    					Console.WriteLine("执行线程池"+ index?.ToString());
    				}), i.ToString());
    			}
    			// 主线程将在方法在线程池线程上运行之前退出。 线程池使用后台线程,如果所有前台线程都终止,后台线程不会使应用程序保持运行。 (这是一个争用条件的简单示例。) 
    			Thread.Sleep(5000);
    		}
    
    		/// <summary>
    		/// 线程池 ThreadPool1
    		/// 主线程将在方法在线程池线程上运行之前退出。 线程池使用后台线程,如果所有前台线程都终止,后台线程不会使应用程序保持运行。 (这是一个争用条件的简单示例。) 
    		/// https://www.cnblogs.com/DonetRen/p/10167095.html
    		/// </summary>
    		// 多线下原子操作操作
    		private static int _count = 100;
    		public static void TestThreadPool1()
    		{
    			// 设置正在等待线程的事件为终止; 错误的使用(如忘记调用 Set())会导致线程永久阻塞(死锁)
    			using AutoResetEvent autoEvent = new AutoResetEvent(false);
    			//执行线程池
    			for (int i = 0; i < 100; i++)
    			{
    				ThreadPool.QueueUserWorkItem(new WaitCallback((object? index) =>
    				{
    					Thread.Sleep(1000);
    					Console.WriteLine("执行线程池" + _count);
    					// 原子操作 所有线程执行完毕后,释放锁,主线程WaitOne 处继续执行 // 将事件的状态设置为信号,允许一个或多个等待线程继续。
    					Interlocked.Decrement(ref _count);
    					if(_count <= 0)
    					{
    						autoEvent.Set();
    					}
    				}), i.ToString());
    			}
    			// 阻止当前线程,直到当前 WaitHandle 收到信号。(等待完毕后,自动重置为 non-signaled。ManualResetEvent是手动复位)
    			//autoEvent.WaitOne();
    			// 超时自动解锁,防止死锁
    			autoEvent.WaitOne(4000);
    		}
    
    
    		/// <summary>
    		/// 线程 Task
    		/// 主线程将在方法在线程池线程上运行之前退出。 线程池使用后台线程,如果所有前台线程都终止,后台线程不会使应用程序保持运行。 (这是一个争用条件的简单示例。) 
    		/// </summary>
    		public static void TestTask()
    		{
    			Task task = new Task(() =>
    			{
    				Thread.Sleep(1000);
    				Console.WriteLine("task");
    			});
    
    			task.Start();
    			
    			// var taskList = new List<Task>();
    			// TODO 将任务列表进行启动,或在初始化时就进行 new Task.Run
    			// 等待任意一个任务完成 (支持设置等待超时时间,防止死锁)
    			// await Task.WhenAny(taskList);
    			// 等待所有任务完成 (支持设置等待超时时间,防止死锁)
                // Task.WaitAll(taskList.ToArray());
    			Console.WriteLine($"task.Status:{task.Status}");
    			Thread.Sleep(1000);
    			Console.WriteLine($"task.Status:{task.Status}");
    		}
    
    		/// <summary>
    		/// 异步编程模型 Task
    		/// 主线程将在方法在线程池线程上运行之前退出。 线程池使用后台线程,如果所有前台线程都终止,后台线程不会使应用程序保持运行。 (这是一个争用条件的简单示例。) 
    		/// </summary>
    		public static async Task  TestTask1()
    		{
    			await Task.Delay(1000);
    			Console.WriteLine(nameof(TestTask1));
    		}
    
    		/// <summary>
    		/// 并行编程 Parallel
    		/// </summary>
    		public static void TestParallel()
    		{
    			Stopwatch swTask = new Stopwatch();
    			swTask.Start();
    			Parallel.For(0, 1000,(int i) =>
    			{
    				Console.WriteLine(i);
    			});
    			swTask.Stop();
    			Console.WriteLine("并行编程所耗时间:" + (swTask.ElapsedMilliseconds / 1000f));
    			swTask.Restart();
    			Parallel.Invoke(() =>
    			{
    				Console.WriteLine(1);
    			}, () =>
    			{
    				Console.WriteLine(2);
    			}, () =>
    			{
    				Console.WriteLine(3);
    			}, () =>
    			{
    				Console.WriteLine(4);
    			}, () =>
    			{
    				Console.WriteLine(5);
    			});
    			swTask.Stop();
    			Console.WriteLine("并行编程所耗时间:" + (swTask.ElapsedMilliseconds / 1000f));
    			swTask.Restart();
    
    			Parallel.ForEach(new List<int>()
    			{
    				0,1,2,3,4
    			}, (int i) =>
    			{
    				Console.WriteLine(i);
    			});
    
    			swTask.Stop();
    			Console.WriteLine("并行编程所耗时间:" + (swTask.ElapsedMilliseconds / 1000f));
    		}
    
    		/// <summary>
    		/// 后台进程 BackgroundWorker  支持取消、支持异常处理
    		/// 主要用在 UI 界面操作上
    		/// </summary>
    		public static void TestBackgroundWorker()
    		{
    			// 无参
    			BackgroundWorker backgroundWorker = new BackgroundWorker();
    			backgroundWorker.DoWork += (object? sender, DoWorkEventArgs e) =>
    			{
    				Console.WriteLine(e.Argument);
    			};
    			backgroundWorker.RunWorkerAsync();
    			// 传参
    			BackgroundWorker backgroundWorker1 = new BackgroundWorker();
    			backgroundWorker1.DoWork += (object? sender, DoWorkEventArgs e) =>
    			{
    				Console.WriteLine(e.Argument);
    			};
    			backgroundWorker1.RunWorkerAsync(100);
    			// 传递给UI
    			BackgroundWorker backgroundWorker2 = new BackgroundWorker();
    			backgroundWorker2.ProgressChanged += (object? sender, ProgressChangedEventArgs e) =>
    			{
    				Console.WriteLine(e.ProgressPercentage);
    				Console.WriteLine(e.UserState);
    			};
    			backgroundWorker2.DoWork += (object? sender, DoWorkEventArgs e) =>
    			{
    				BackgroundWorker? bgWorker = sender as BackgroundWorker;
    				for (int i = 0; i < 100; i++)
    				{
    					// 不传递消息
    					// bgWorker?.ReportProgress(i);
    					// 传递消息
    					bgWorker?.ReportProgress(i, "进度已更新");
    					Thread.Sleep(10);
    				}
    			};
    			// 设置WorkerReportsProgress=true才可以ReportProgress;  https://www.pianshen.com/ask/94349591088/
    			backgroundWorker2.WorkerReportsProgress = true;
    			backgroundWorker2.RunWorkerAsync(100);
    			Thread.Sleep(5000);
    
    		}
    	}
    }
    
    

各方式优缺点分析

实现方式 优点 缺点
Thread 1. 精细控制线程属性(优先级、名称)
2. 适合长期运行任务
1. 创建开销大(1MB/线程)
2. 需手动管理生命周期
3. 易出现闭包问题
ThreadPool 1. 线程复用降低开销
2. 自动管理线程生命周期
3. 适合短时任务
1. 最大线程数有限(默认1023)
2. 无法控制具体线程
3. 后台线程不阻止进程退出
Task 1. 支持async/await异步模型
2. 内置取消机制
3. 异常处理完善
1. 学习曲线较陡
2. 错误使用可能导致死锁(如.Result阻塞)
Parallel 1. 自动负载均衡
2. 简化数据并行操作
3. 适合CPU密集型循环
1. 不保证执行顺序
2. 共享资源需同步
3. 不适合I/O密集型操作
BackgroundWorker 1. 内置进度报告
2. 自动跨线程更新UI
3. 支持取消操作
1. 主要适用于WinForms/WPF
2. 已被async/await替代
3. 控制台应用效果有限

文章代码关键问题补充

  1. 闭包问题TestThreads 方法)

    for (int i = 0; i < 10; i++) {
        Thread thread = new Thread(() => {
            // 这里i可能不是预期值(闭包捕获循环变量)
            Console.WriteLine("Thread" + i); 
        });
    }
    

    修复方案

    for (int i = 0; i < 10; i++) {
        int local = i; // 创建局部副本
        new Thread(() => Console.WriteLine("Thread" + local)).Start();
    }
    
  2. 线程同步机制TestThreadPool1

    • 使用 AutoResetEvent + Interlocked 实现原子计数
    • 改进建议:对于计数场景,推荐使用 CountdownEvent
      var countdown = new CountdownEvent(100);
      for (int i = 0; i < 100; i++) {
          ThreadPool.QueueUserWorkItem(_ => {
              // ...
              countdown.Signal();
          });
      }
      countdown.Wait(); // 等待所有完成
      
  3. 现代替代方案BackgroundWorker

    // 使用Task替代BackgroundWorker
    var progress = new Progress<int>(percent => 
        Console.WriteLine($"进度: {percent}%"));
    
    await Task.Run(() => {
        for (int i = 0; i < 100; i++) {
            ((IProgress<int>)progress).Report(i);
            Thread.Sleep(10);
        }
    });
    

示例代码未包含的重要多线程技术

  1. 异步流(Async Streams)

    public async IAsyncEnumerable<int> GetAsyncStream()
    {
        for (int i = 0; i < 100; i++) {
            await Task.Delay(100);
            yield return i;
        }
    }
    
    await foreach (var item in GetAsyncStream()) {
        Console.WriteLine(item);
    }
    
  2. 通道(System.Threading.Channels)

    var channel = Channel.CreateUnbounded<int>();
    
    // 生产者
    _ = Task.Run(async () => {
        for (int i = 0; i < 10; i++) {
            await channel.Writer.WriteAsync(i);
        }
        channel.Writer.Complete();
    });
    
    // 消费者
    await foreach (var item in channel.Reader.ReadAllAsync()) {
        Console.WriteLine($"Received: {item}");
    }
    
  3. 并行LINQ(PLINQ)

    var results = data.AsParallel()
                     .Where(x => x.IsValid)
                     .Select(x => x.Process())
                     .ToList();
    

多线程最佳实践

  1. 资源同步

    • 使用 lock 保护共享资源
    • 使用线程安全集合(ConcurrentBag<T>, BlockingCollection<T>
  2. 取消机制

    var cts = new CancellationTokenSource();
    var token = cts.Token;
    
    Task.Run(() => {
        while (!token.IsCancellationRequested) {
            // 工作
        }
    }, token);
    
    cts.CancelAfter(5000); // 5秒后取消
    
  3. 异常处理

    try {
        await Task.Run(() => RiskyOperation());
    }
    catch (AggregateException ae) {
        ae.Handle(e => e is OperationCanceledException);
    }
    

线程选择指南(更新版)

场景 推荐方案 代码示例
UI响应式后台任务 async/await + IProgress await Task.Run(() => {...}, progress)
数据并行处理 Parallel/PLINQ Parallel.For(0, N, i => {...})
短期后台任务 ThreadPool.QueueUserWorkItem ThreadPool.QueueUserWorkItem(WorkMethod)
长期独立任务 Thread new Thread(WorkMethod){IsBackground=true}
生产者-消费者模式 Channels + async Channel.CreateBounded<T>()
定时任务 Timer + async new Timer(async _ => await WorkAsync())

重要提示:现代C#开发中,推荐优先使用 Task + async/await 组合,它覆盖了95%的并发场景,同时保持代码可读性和可维护性。保留 Thread 仅用于需要精细控制线程的特殊场景。

posted @ 2025-06-07 00:35  夏秋初  阅读(19)  评论(0)    收藏  举报