51、.NET 多线程编程指南

.NET 多线程编程指南

1. 异步类选择优先级

在 .NET 编程中,选择合适的异步类对于多线程编程至关重要。一般来说,选择异步类的优先级顺序为:Task、ThreadPool 和 Thread。具体解释如下:
- Task :优先使用 Task Parallel Library (TPL),它基于 System.Threading.Tasks.Task 类,提供了标准的多线程编程和监控活动,使多线程编程相对简单。Task 是并行循环(Parallel.For() 和 Parallel.ForEach())、PLINQ 等的基础,能实现更复杂的线程场景,如异常处理和任务链/通知。
- ThreadPool :如果 TPL 不适用,可考虑使用 ThreadPool。它能有效管理线程创建,通过 CLR 的线程池 System.Threading.ThreadPool 动态决定何时使用现有线程而非创建新线程,提高了线程的复用效率。
- Thread :若 ThreadPool 仍无法满足需求,则使用 Thread。

例如,当需要暂停线程时,由于 Task 和 ThreadPool 没有等效方法,可能会使用 Thread.Sleep()。不过,如果不会引入过多不必要的复杂性,可考虑使用定时器代替 Sleep()。

2. 线程池的使用

线程池是提高多线程编程效率的重要工具,但也存在一些需要注意的地方。
- 优点 :线程池能动态决定何时使用现有线程,避免了频繁创建新线程的开销,提高了执行效率。例如,以下代码展示了如何使用线程池:

using System; 
using System.Threading;
public class Program 
{
    public const int Repetitions = 1000;
    public static void Main()
    {
        for (int count = 0; count < Repetitions; count++)
        {
            ThreadPool.QueueUserWorkItem(DoWork, '.');
            Console.Write('-');
        }
        // Pause until the thread completes
    }
    public static void DoWork(object state)
    {
        for (int count = 0; count < Repetitions; count++)
        {
            Console.Write(state);
        }
    } 
}

该代码的输出是 . - 的混合,通过复用线程,在单处理器和多处理器计算机上都能实现更高效的执行。
- 缺点
- 线程耗尽 :I/O 操作和其他内部使用线程池的框架方法可能会消耗线程,当线程池中的所有线程都被消耗时,会导致执行延迟,极端情况下可能会造成死锁。
- 缺乏控制 :ThreadPool API 不返回线程或任务的句柄,调用线程无法使用线程管理函数对其进行控制,若不添加自定义实现,也无法监控线程状态。

3. 应用程序域中的未处理异常

在多线程编程中,处理未处理异常是一个重要问题。当第三方组件创建的线程或线程池中的排队工作抛出未处理异常时,Main() 中的 try/catch 块无法捕获这些异常。为了在出现未处理异常时保存工作数据和记录异常信息,可通过应用程序域的 UnhandledException 事件进行注册。示例代码如下:

using System; 
using System.Threading;
public class Program 
{
    public static void Main()
    {
        try
        {
            // Register a callback to receive notifications of any unhandled exception.
            AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
            ThreadPool.QueueUserWorkItem(
                state => 
                { 
                    throw new Exception(
                        "Arbitrary Exception"); 
                });
            // ...
            // Wait for the unhandled exception to fire
            // ADVANCED: Use ManualResetEvent to avoid timing dependent code.
            Thread.Sleep(10000);
            Console.WriteLine("Still running...");
        }
        finally
        {
            Console.WriteLine("Exiting...");
        }
    }
    public static void ThrowException()
    {
        throw new ApplicationException(
            "Arbitrary exception");
    } 
    static void OnUnhandledException(
        object sender, 
        UnhandledExceptionEventArgs eventArgs)
    {
        Exception exception = 
            (Exception)eventArgs.ExceptionObject;
        Console.WriteLine("ERROR ({0}):{1} ---> {2}",
            exception.GetType().Name,
            exception.Message, 
            exception.InnerException.Message);
    }
}

UnhandledException 回调会在应用程序域内的所有线程(包括主线程)抛出未处理异常时触发,但它只是一个通知机制,无法捕获和处理异常以让应用程序继续运行。事件触发后,应用程序将退出。

4. 多线程同步问题

多线程编程的难点之一是识别多个线程可能同时访问的数据,并对这些数据进行同步,以防止同时访问导致的数据完整性问题。
- 未同步状态示例 :以下代码展示了未同步状态下的多线程问题:

using System; 
using System.Threading.Tasks;
class Program 
{
    const int _Total = int.MaxValue;
    static long _Count = 0;
    public static void Main()
    {
        Task task = Task.Factory.StartNew(Decrement);
        // Increment
        for (int i = 0; i < _Total; i++)
        {
            _Count++;
        }
        task.Wait();
        Console.WriteLine("Count = {0}", _Count);
    }
    static void Decrement()
    {
        // Decrement
        for (int i = 0; i < _Total; i++)
        {
            _Count--;
        }
    } 
}

该代码的输出通常不是 0,因为 _Count++ _Count-- 语句的各个步骤可能会相互交织,导致竞态条件。以下是一个示例执行过程:
| 主线程 | 递减线程 | Count |
| — | — | — |
| 从 _Count 中复制值 0 | | 0 |
| 将复制的值(0)加 1,结果为 1 | | 0 |
| 将结果值(1)复制到 _Count | | 1 |
| 从 _Count 中复制值 1 | 从 _Count 中复制值 1 | 1 |
| 将复制的值(1)加 1,结果为 2 | | 1 |
| 将结果值(2)复制到 _Count | | 2 |
| | 将复制的值(1)减 1,结果为 0 | 2 |
| | 将结果值(0)复制到 _Count | 0 |

  • 局部变量的并发问题 :虽然局部变量默认不共享,但如果代码将局部变量暴露给多个线程,也会引发并发问题。例如,以下代码中的局部变量 x 在并行循环中被多个线程同时修改,导致竞态条件:
using System; 
using System.Threading.Tasks;
class Program 
{
    public static void Main()
    {
        int x = 0;
        Parallel.For(0, int.MaxValue, i =>
        {
            x++;
            x--;
        });
        Console.WriteLine("Count = {0}", x);
    } 
}
5. 使用 Monitor 进行同步

为了解决多线程同步问题,可以使用 System.Threading.Monitor 类。通过调用 Monitor.Enter() 和 Monitor.Exit() 方法,可以标记受保护的代码段,确保同一时间只有一个线程可以执行该代码段。以下是使用 Monitor 进行同步的示例代码:

using System; 
using System.Threading; 
using System.Threading.Tasks;
class Program 
{
    const int _Total = int.MaxValue;
    static long _Count = 0;
    readonly static object _Sync = new object();
    public static void Main()
    {
        Task task = Task.Factory.StartNew(Decrement);
        // Increment
        for (int i = 0; i < _Total; i++)
        {
            bool lockTaken = false;
            Monitor.Enter(_Sync, ref lockTaken);
            try
            {
                _Count++;
            }
            finally
            {
                if (lockTaken)
                {
                    Monitor.Exit(_Sync);
                }
            }
        }
        task.Wait();
        Console.WriteLine("Count = {0}", _Count);
    }
    static void Decrement()
    {
        for (int i = 0; i < _Total; i++)
        {
            bool lockTaken = false;
            Monitor.Enter(_Sync, ref lockTaken);
            try
            {
                _Count--;
            }
            finally
            {
                if (lockTaken)
                {
                    Monitor.Exit(_Sync);
                }
            }
        }
    } 
}

该代码的输出为 Count = 0 ,表明通过 Monitor 同步,避免了竞态条件。

需要注意的是,在 .NET 4.0 中,Monitor.Enter() 方法添加了 lockTaken 参数,用于可靠地捕获异常。在之前的版本中,由于缺乏该参数,可能会导致锁泄漏和死锁问题。

此外,Monitor 还支持 Pulse() 方法,用于同步生产者 - 消费者模式。生产者线程调用 Monitor.Pulse() 通知消费者线程有可用的项目,消费者线程在生产者线程调用 Monitor.Exit() 后获取锁并开始处理项目,确保了生产和消费的顺序。

.NET 多线程编程指南(下半部分)

6. 更多同步类型及方法

除了 Monitor 之外,还有其他一些同步类型和方法可用于多线程编程,下面为你详细介绍:
- Lock 关键字 :Lock 关键字是 Monitor 的语法糖,使用起来更加简洁。它本质上是对 Monitor.Enter() 和 Monitor.Exit() 的封装。示例代码如下:

using System; 
using System.Threading.Tasks;
class Program 
{
    const int _Total = int.MaxValue;
    static long _Count = 0;
    readonly static object _Sync = new object();
    public static void Main()
    {
        Task task = Task.Factory.StartNew(Decrement);
        // Increment
        for (int i = 0; i < _Total; i++)
        {
            lock (_Sync)
            {
                _Count++;
            }
        }
        task.Wait();
        Console.WriteLine("Count = {0}", _Count);
    }
    static void Decrement()
    {
        for (int i = 0; i < _Total; i++)
        {
            lock (_Sync)
            {
                _Count--;
            }
        }
    } 
}
  • Volatile :Volatile 关键字用于确保字段的读写操作直接在内存中进行,而不是使用 CPU 缓存。这可以避免因缓存不一致导致的问题。例如:
class Program 
{
    private static volatile bool _isRunning = true;
    public static void Main()
    {
        Task.Run(() =>
        {
            while (_isRunning)
            {
                // Do some work
            }
        });
        // Stop the task after some time
        _isRunning = false;
    }
}
  • Mutex :Mutex(互斥体)是一种跨进程的同步原语,用于确保同一时间只有一个线程或进程可以访问共享资源。示例代码如下:
using System; 
using System.Threading;
class Program 
{
    private static Mutex _mutex = new Mutex();
    public static void Main()
    {
        _mutex.WaitOne();
        try
        {
            // Access the shared resource
        }
        finally
        {
            _mutex.ReleaseMutex();
        }
    }
}
  • WaitHandle :WaitHandle 是一个抽象基类,用于等待一个或多个等待句柄变为有信号状态。常见的子类有 ManualResetEvent 和 AutoResetEvent。
    • ManualResetEvent :可以手动设置和重置信号状态。示例代码如下:
using System; 
using System.Threading;
class Program 
{
    private static ManualResetEvent _mre = new ManualResetEvent(false);
    public static void Main()
    {
        Task.Run(() =>
        {
            _mre.WaitOne();
            // Do some work after the signal
        });
        // Set the signal after some time
        _mre.Set();
    }
}
- **AutoResetEvent**:在等待线程收到信号后会自动重置信号状态。示例代码如下:
using System; 
using System.Threading;
class Program 
{
    private static AutoResetEvent _are = new AutoResetEvent(false);
    public static void Main()
    {
        Task.Run(() =>
        {
            _are.WaitOne();
            // Do some work after the signal
        });
        // Set the signal after some time
        _are.Set();
    }
}
7. 多线程模式

在多线程编程中,有一些常见的模式可以帮助我们更好地组织和管理线程,以下是几种常见的模式:
- 生产者 - 消费者模式 :生产者线程负责生产数据,消费者线程负责消费数据。可以使用 Monitor 的 Pulse() 和 Exit() 方法来实现生产和消费的同步。示例代码如下:

using System; 
using System.Collections.Generic;
using System.Threading;
class Program 
{
    private static Queue<int> _queue = new Queue<int>();
    private static readonly object _lock = new object();
    private static bool _isProducing = true;
    public static void Main()
    {
        Task.Run(Producer);
        Task.Run(Consumer);
        // Stop producing after some time
        Thread.Sleep(5000);
        _isProducing = false;
    }
    static void Producer()
    {
        while (_isProducing)
        {
            lock (_lock)
            {
                _queue.Enqueue(new Random().Next(100));
                Monitor.Pulse(_lock);
            }
            Thread.Sleep(100);
        }
    }
    static void Consumer()
    {
        while (true)
        {
            lock (_lock)
            {
                while (_queue.Count == 0)
                {
                    Monitor.Wait(_lock);
                }
                int item = _queue.Dequeue();
                Console.WriteLine("Consumed: " + item);
            }
        }
    }
}
  • 线程本地存储(Thread Local Storage) :线程本地存储允许每个线程拥有自己独立的变量副本。可以使用 ThreadLocal 类来实现。示例代码如下:
using System; 
using System.Threading;
class Program 
{
    private static ThreadLocal<int> _threadLocal = new ThreadLocal<int>(() => 0);
    public static void Main()
    {
        Task.Run(() =>
        {
            _threadLocal.Value++;
            Console.WriteLine("Thread 1: " + _threadLocal.Value);
        });
        Task.Run(() =>
        {
            _threadLocal.Value++;
            Console.WriteLine("Thread 2: " + _threadLocal.Value);
        });
    }
}
  • 定时器(Timers) :定时器可以在指定的时间间隔内执行任务。在 .NET 中有多种定时器可供选择,如 System.Timers.Timer、System.Threading.Timer 和 System.Windows.Forms.Timer。以下是 System.Threading.Timer 的示例代码:
using System; 
using System.Threading;
class Program 
{
    private static Timer _timer;
    public static void Main()
    {
        _timer = new Timer(Callback, null, 0, 1000);
        Console.WriteLine("Press any key to exit...");
        Console.ReadKey();
        _timer.Dispose();
    }
    static void Callback(object state)
    {
        Console.WriteLine("Timer ticked at: " + DateTime.Now);
    }
}
8. 异步编程模型

异步编程模型可以提高程序的响应性和性能,尤其是在处理 I/O 密集型任务时。在 .NET 中,可以使用 async 和 await 关键字来实现异步编程。示例代码如下:

using System; 
using System.Threading.Tasks;
class Program 
{
    public static async Task Main()
    {
        Task<int> task = LongRunningTask();
        // Do some other work while the task is running
        Console.WriteLine("Doing other work...");
        int result = await task;
        Console.WriteLine("Task result: " + result);
    }
    static async Task<int> LongRunningTask()
    {
        await Task.Delay(2000);
        return 42;
    }
}
9. 背景工作者模式

背景工作者模式用于在后台线程中执行耗时的任务,同时保持 UI 的响应性。在 Windows 应用程序中,可以使用 BackgroundWorker 类。示例代码如下:

using System; 
using System.ComponentModel;
using System.Windows.Forms;
namespace BackgroundWorkerExample
{
    public partial class Form1 : Form
    {
        private BackgroundWorker _backgroundWorker;
        public Form1()
        {
            InitializeComponent();
            _backgroundWorker = new BackgroundWorker();
            _backgroundWorker.DoWork += BackgroundWorker_DoWork;
            _backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
        }
        private void button1_Click(object sender, EventArgs e)
        {
            _backgroundWorker.RunWorkerAsync();
        }
        private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            // Do some long-running work
            Thread.Sleep(5000);
        }
        private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            MessageBox.Show("Task completed!");
        }
    }
}
10. Windows UI 编程中的多线程

在 Windows UI 编程中,由于 UI 线程负责绘制和响应用户交互,因此耗时的任务应该在后台线程中执行,以避免 UI 冻结。可以使用 Dispatcher 来更新 UI 元素。示例代码如下:

using System; 
using System.Threading.Tasks;
using System.Windows;
namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            await Task.Run(() =>
            {
                // Do some long-running work
                Thread.Sleep(5000);
                // Update the UI on the UI thread
                this.Dispatcher.Invoke(() =>
                {
                    MessageBox.Show("Task completed!");
                });
            });
        }
    }
}
11. 总结

在 .NET 多线程编程中,我们可以根据不同的需求选择合适的异步类,如 Task、ThreadPool 和 Thread。同时,要注意处理未处理异常,避免因异常导致程序崩溃。在多线程同步方面,有多种同步类型和方法可供选择,如 Monitor、Lock、Volatile、Mutex 等。此外,还可以使用各种多线程模式和异步编程模型来提高程序的性能和响应性。

在实际开发中,要根据具体的场景选择合适的同步方法和模式,避免死锁和竞态条件的发生。同时,要注意线程安全,确保共享数据的完整性。希望通过本文的介绍,你能对 .NET 多线程编程有更深入的理解和掌握。

【轴承故障诊断】加权多尺度字典学习模型(WMSDL)及其在轴承故障诊断上的应用(Matlab代码实现)内容概要:本文介绍了加权多尺度字典学习模型(WMSDL)在轴承故障诊断中的应用,并提供了基于Matlab的代码实现。该模型结合多尺度分析与字典学习技术,能够有效提取轴承振动信号中的故障特征,提升故障识别精度。文档重点阐述了WMSDL模型的理论基础、算法流程及其在实际故障诊断中的实施步骤,展示了其相较于传统方法在特征表达能力和诊断准确性方面的优势。同时,文中还提及该资源属于一个涵盖多个科研方向的技术合集,包括智能优化算法、机器学习、信号处理、电力系统等多个领域的Matlab仿真案例。; 适合人群:具备一定信号处理和机器学习基础,从事机械故障诊断、工业自动化、智能制造等相关领域的研究生、科研人员及工程技术人员。; 使用场景及目标:①学习并掌握加权多尺度字典学习模型的基本原理与实现方法;②将其应用于旋转机械的轴承故障特征提取与智能诊断;③结合实际工程数据复现算法,提升故障诊断系统的准确性和鲁棒性。; 阅读建议:建议读者结合提供的Matlab代码进行实践操作,重点关注字典学习的训练过程与多尺度分解的实现细节,同时可参考文中提到的其他相关技术(如VMD、CNN、BILSTM等)进行对比实验与算法优化。
【硕士论文复现】可再生能源发电与电动汽车的协同调度策略研究(Matlab代码实现)内容概要:本文档围绕“可再生能源发电与电动汽车的协同调度策略研究”展开,旨在通过Matlab代码复现硕士论文中的核心模型与算法,探讨可再生能源(如风电、光伏)与大规模电动汽车接入电网后的协同优化调度方法。研究重点包括考虑需求侧响应的多时间尺度调度、电动汽车集群有序充电优化、源荷不确定性建模及鲁棒优化方法的应用。文中提供了完整的Matlab实现代码与仿真模型,涵盖从场景生成、数学建模到求解算法(如NSGA-III、粒子群优化、ADMM等)的全过程,帮助读者深入理解微电网与智能电网中的能量管理机制。; 适合人群:具备一定电力系统基础知识和Matlab编程能力的研究生、科研人员及从事新能源、智能电网、电动汽车等领域技术研发的工程人员。; 使用场景及目标:①用于复现和验证硕士论文中的协同调度模型;②支撑科研工作中关于可再生能源消纳、电动汽车V2G调度、需求响应机制等课题的算法开发与仿真验证;③作为教学案例辅助讲授能源互联网中的优化调度理论与实践。; 阅读建议:建议结合文档提供的网盘资源下载完整代码,按照目录顺序逐步学习各模块实现,重点关注模型构建逻辑与优化算法的Matlab实现细节,并通过修改参数进行仿真实验以加深理解。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值