当前位置:首页 > 科技  > 软件

Net开发,跨线程安全通信,注意那些容易出错的地方

来源: 责编: 时间:2024-01-08 17:10:16 334观看
导读跨线程安全通信在.Net开发中需要特别注意共享数据、线程同步、死锁、线程安全性、线程调度、异步编程以及内存管理等方面的问题。合理设计和实施跨线程通信策略,并进行充分的测试和验证,以确保程序的正确性和可靠性。下

2W728资讯网——每日最新资讯28at.com

跨线程安全通信在.Net开发中需要特别注意共享数据、线程同步、死锁、线程安全性、线程调度、异步编程以及内存管理等方面的问题。合理设计和实施跨线程通信策略,并进行充分的测试和验证,以确保程序的正确性和可靠性。下面详细举例说明在进行跨线程安全通信的.Net开发中,一些容易出错的地方:2W728资讯网——每日最新资讯28at.com

1、共享数据访问:

多个线程同时访问共享数据可能导致数据不一致。需要确保在访问和修改共享数据时进行正确的同步操作,例如使用锁或其他同步机制来保证数据的正确性。2W728资讯网——每日最新资讯28at.com

using System;using System.Threading;class Program{    static int sharedData = 0;    static object lockObj = new object();    static void Main(string[] args)    {        // 创建并启动多个线程        Thread[] threads = new Thread[5];        for (int i = 0; i < threads.Length; i++)        {            threads[i] = new Thread(IncrementSharedData);            threads[i].Start();        }        // 等待所有线程执行完成        foreach (var thread in threads)        {            thread.Join();        }        Console.WriteLine("Final value of sharedData: " + sharedData);    }    static void IncrementSharedData()    {        for (int i = 0; i < 10000; i++)        {            lock (lockObj) // 使用锁来保证同步操作            {                sharedData++;            }        }    }}

上述代码创建了5个线程,并在每个线程中对共享的数据 sharedData 进行递增操作。如果没有加锁保护,多个线程同时访问时会导致数据不一致的问题。通过在访问 sharedData 时添加 lock 块来确保同步操作,保证了每个线程在访问/修改共享数据时互斥进行。这样可以避免竞态条件,确保数据的正确性。2W728资讯网——每日最新资讯28at.com

输出结果是 50000,表示共享数据被并发地递增了50000次。如果没有使用锁来保护共享数据,最终的结果可能小于50000,因为多个线程之间相互干扰并导致数据不一致。2W728资讯网——每日最新资讯28at.com

2、死锁:

死锁是指两个或多个线程互相等待对方释放资源而无法继续执行的情况。在进行跨线程通信时,需要避免出现死锁情况,合理设计线程间的依赖关系和资源占用顺序,避免循环等待的情况发生。2W728资讯网——每日最新资讯28at.com

using System;using System.Threading;class Program{    static object lockObj1 = new object();    static object lockObj2 = new object();    static void Main(string[] args)    {        Thread thread1 = new Thread(Method1);        Thread thread2 = new Thread(Method2);        thread1.Start();        thread2.Start();        thread1.Join();        thread2.Join();        Console.WriteLine("Program completed.");    }    static void Method1()    {        lock (lockObj1)        {            Console.WriteLine("Thread 1 acquired lockObj1");            Thread.Sleep(1000);            lock (lockObj2)            {                Console.WriteLine("Thread 1 acquired lockObj2");                // 执行操作...            }        }    }    static void Method2()    {        lock (lockObj2)        {            Console.WriteLine("Thread 2 acquired lockObj2");            Thread.Sleep(1000);            lock (lockObj1)            {                Console.WriteLine("Thread 2 acquired lockObj1");                // 执行操作...            }        }    }}

在上述代码中,Method1 和 Method2 方法分别获取 lockObj1 和 lockObj2 的锁。如果线程1先获取了 lockObj1 的锁,然后尝试获取 lockObj2 的锁,同时线程2先获取了 lockObj2 的锁,然后尝试获取 lockObj1 的锁,就会导致死锁的发生。2W728资讯网——每日最新资讯28at.com

为了避免死锁,可以按照固定的顺序获取锁,或者使用 Monitor.TryEnter 方法进行尝试获取锁并设置超时时间。下面是修改后的示例代码:2W728资讯网——每日最新资讯28at.com

using System;using System.Threading;class Program{    static object lockObj1 = new object();    static object lockObj2 = new object();    static void Main(string[] args)    {        Thread thread1 = new Thread(Method1);        Thread thread2 = new Thread(Method2);        thread1.Start();        thread2.Start();        thread1.Join();        thread2.Join();        Console.WriteLine("Program completed.");    }    static void Method1()    {        lock (lockObj1)        {            Console.WriteLine("Thread 1 acquired lockObj1");            Thread.Sleep(1000);            bool lockTaken = false;            try            {                Monitor.TryEnter(lockObj2, TimeSpan.FromSeconds(2), ref lockTaken);                if (lockTaken)                {                    Console.WriteLine("Thread 1 acquired lockObj2");                    // 执行操作...                }                else                {                    Console.WriteLine("Thread 1 failed to acquire lockObj2");                }            }            finally            {                if (lockTaken)                    Monitor.Exit(lockObj2);            }        }    }    static void Method2()    {        bool lockTaken1 = false;        try        {            Monitor.TryEnter(lockObj1, TimeSpan.FromSeconds(2), ref lockTaken1);            if (lockTaken1)            {                Console.WriteLine("Thread 2 acquired lockObj1");                Thread.Sleep(1000);                lock (lockObj2)                {                    Console.WriteLine("Thread 2 acquired lockObj2");                    // 执行操作...                }            }            else            {                Console.WriteLine("Thread 2 failed to acquire lockObj1");            }        }        finally        {            if (lockTaken1)                Monitor.Exit(lockObj1);        }    }}

通过使用 Monitor.TryEnter 方法尝试获取锁,并设置超时时间来避免死锁。如果无法获取到锁,在超时后进行相应的处理。这样即使发生了循环等待的情况,也能够及时中断并避免死锁的发生。2W728资讯网——每日最新资讯28at.com

3、线程安全性:

某些操作可能不是线程安全的,特别是在修改共享数据时。在进行跨线程通信时,必须小心处理可能引发竞态条件或非线程安全问题的代码段,例如使用正确的锁机制来保护临界区域。2W728资讯网——每日最新资讯28at.com

using System;using System.Threading;class Program{    static int counter = 0;    static object lockObj = new object();    static void Main(string[] args)    {        Thread thread1 = new Thread(IncrementCounter);        Thread thread2 = new Thread(IncrementCounter);        thread1.Start();        thread2.Start();        thread1.Join();        thread2.Join();        Console.WriteLine("Counter: " + counter);    }    static void IncrementCounter()    {        for (int i = 0; i < 100000; i++)        {            // 加锁保护临界区域            lock (lockObj)            {                counter++;            }        }    }}

在上述代码中,有两个线程同时对 counter 变量进行递增操作。如果没有使用锁机制保护临界区域,可能会导致竞态条件的问题。竞态条件指的是多个线程对共享数据的竞争,从而导致不确定的结果。2W728资讯网——每日最新资讯28at.com

通过使用 lock 关键字,我们确保在任何时候只有一个线程可以访问临界区域,即对 counter 的递增操作。当一个线程进入临界区域时,其他线程会被阻塞,直到该线程释放锁。这样可以确保安全地修改共享数据。2W728资讯网——每日最新资讯28at.com

注意,在这个特定的案例中,使用锁机制是一种简单且有效的方式来保护临界区域。然而,并不是所有情况都适用于使用锁。在实际开发中,还可以使用其他同步机制,如 Monitor 类、互斥体(Mutex)、信号量等,根据具体需求进行选择。2W728资讯网——每日最新资讯28at.com

4、跨线程调度:

在进行UI线程与后台线程之间的通信时,需要注意使用正确的线程调度机制,以确保在UI界面上正确显示或更新数据。例如,使用Dispatcher.Invoke或Control.Invoke来将操作委托到UI线程上执行。2W728资讯网——每日最新资讯28at.com

using System;using System.Threading;using System.Windows.Forms;class Program{    static void Main(string[] args)    {        // 创建一个UI窗体        Form form = new Form();        Button button = new Button();        form.Controls.Add(button);        // 注册按钮点击事件        button.Click += Button_Click;        // 启动后台线程        Thread thread = new Thread(DoBackgroundWork);        thread.Start(form);        // 运行应用程序的消息循环        Application.Run(form);    }    static void DoBackgroundWork(object state)    {        // 获取UI窗体实例        Form form = (Form)state;        for (int i = 0; i < 10; i++)        {            // 模拟耗时操作            Thread.Sleep(1000);            // 更新UI,需要通过线程调度机制执行在UI线程上            form.Invoke(new Action(() =>            {                form.Text = "Count: " + i.ToString();            }));        }    }    static void Button_Click(object sender, EventArgs e)    {        MessageBox.Show("Button clicked!");    }}

在上述代码中,我们创建了一个包含按钮和文本框的简单窗体。主线程是UI线程,后台线程模拟耗时的操作并更新UI上的计数器。在后台线程中,我们使用 form.Invoke 方法来将更新UI的操作委托到UI线程上执行。这样可以确保更新操作在UI线程上进行,以避免线程安全问题和跨线程访问的异常。2W728资讯网——每日最新资讯28at.com

注意,在使用 Invoke 方法时,传递给它的是一个委托,用于执行需要在UI线程上运行的操作。在本例中,我们使用 Action 委托来简化代码。通过正确使用线程调度机制,可以确保在UI界面上正确显示或更新数据,并保持与UI线程的正确通信。2W728资讯网——每日最新资讯28at.com

5、异步/并发编程:

异步和并发编程在跨线程通信中经常被使用,但也容易引发各种问题。需要小心处理异步回调、任务取消、数据共享等相关问题,确保异步操作的稳定性和一致性。2W728资讯网——每日最新资讯28at.com

using System;using System.Threading;using System.Threading.Tasks;class Program{    static async Task Main(string[] args)    {        // 创建一个资源对象,用于数据共享        SharedResource resource = new SharedResource();        // 运行异步操作并获取任务对象        Task operationTask = PerformAsyncOperation(resource);        // 模拟一段时间后取消异步操作        await Task.Delay(2000);        CancelAsyncOperation(operationTask);        // 等待异步操作完成        await operationTask;        Console.WriteLine("Async operation completed: " + resource.Data);    }    static async Task PerformAsyncOperation(SharedResource resource)    {        try        {            // 模拟耗时操作            await Task.Delay(5000);            // 使用资源进行计算            int result = resource.CalculateData();            // 更新共享数据            resource.Data = result.ToString();            Console.WriteLine("Async operation completed successfully.");        }        catch (TaskCanceledException)        {            Console.WriteLine("Async operation was canceled.");        }        catch (Exception ex)        {            Console.WriteLine("Async operation failed: " + ex.Message);        }    }    static void CancelAsyncOperation(Task operationTask)    {        if (!operationTask.IsCompleted && !operationTask.IsCanceled)        {            // 取消异步操作            CancellationTokenSource cts = new CancellationTokenSource();            cts.Cancel();            operationTask.ContinueWith(task =>            {                if (task.IsCanceled)                {                    Console.WriteLine("Async operation canceled.");                }            }, TaskScheduler.Default);        }    }}class SharedResource{    public string Data { get; set; }    public int CalculateData()    {        // 模拟复杂的计算过程        Thread.Sleep(3000);        return 42;    }}

在上述代码中,我们有一个异步操作 PerformAsyncOperation,它使用一个共享资源 SharedResource 进行计算,并更新共享数据。我们通过创建一个 CancellationTokenSource 对象并取消该任务来模拟异步操作的取消。2W728资讯网——每日最新资讯28at.com

在 Main 方法中,我们运行异步操作 PerformAsyncOperation 并等待一段时间后取消它。我们使用 CancelAsyncOperation 方法来取消异步操作。注意,这里通过调用 ContinueWith 方法来检查异步任务是否已被取消。在异步操作中,我们捕获了 TaskCanceledException 异常,以处理异步操作被取消的情况,并在其他异常情况下进行适当的错误处理。通过小心处理异步回调、任务取消和数据共享等相关问题,可以确保异步操作的稳定性和一致性,并避免潜在的问题。2W728资讯网——每日最新资讯28at.com

6、内存管理:

跨线程通信可能涉及到内存资源的共享和释放,需要特别注意正确的内存管理。避免内存泄漏、非法访问已释放的资源等问题。2W728资讯网——每日最新资讯28at.com

using System;using System.Threading;class Program{    static void Main(string[] args)    {        // 创建一个线程并启动        Thread thread = new Thread(WorkThread);        thread.Start();        // 等待一段时间后请求停止线程        Thread.Sleep(2000);        StopThread(thread);        // 等待线程完成        thread.Join();        Console.WriteLine("Main thread completed.");    }    static void WorkThread()    {        // 创建一个资源对象        Resource resource = new Resource();        try        {            while (!resource.IsCancelled)            {                // 模拟耗时操作                Thread.Sleep(500);                // 使用资源进行工作                resource.DoWork();            }        }        finally        {            // 确保正确释放资源            resource.Dispose();        }    }    static void StopThread(Thread thread)    {        // 请求停止线程        Resource resource = (Resource)thread;        resource.Cancel();    }}class Resource : IDisposable{    private bool _isCancelled;    public bool IsCancelled { get => _isCancelled; }    public void DoWork()    {        // 使用资源进行工作        Console.WriteLine("Working...");    }    public void Cancel()    {        _isCancelled = true;    }    public void Dispose()    {        // 释放资源        Console.WriteLine("Disposing resource...");    }}

在上述代码中,我们创建了一个工作线程,并在该线程中使用资源对象执行工作。资源对象实现了 IDisposable 接口,以确保在不再使用资源时正确释放它。在工作线程中,我们使用了一个循环来执行工作操作,直到资源对象被取消。在每次迭代中,我们都会检查资源的取消状态,并根据需要执行相应的操作。2W728资讯网——每日最新资讯28at.com

在 Main 方法中,我们等待一段时间后请求停止线程,通过将资源对象强制转换为 Resource 类型来调用 Cancel 方法。这会将 IsCancelled 属性设置为 true,从而终止循环并使工作线程退出。经过演示,可以确保资源对象在使用完毕后正确释放,避免了内存泄漏和非法访问已释放的资源。2W728资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-58897-0.htmlNet开发,跨线程安全通信,注意那些容易出错的地方

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 如何自己实现一个静态代码分析工具?

下一篇: 线程池系统设置最全指南!

标签:
  • 热门焦点
  • K60至尊版狂暴引擎2.0加持:超177万跑分斩获性能第一

    Redmi的后性能时代战略发布会今天下午如期举办,在本次发布会上,Redmi公布了多项关于和联发科的深度合作,以及新机K60 Ultra在软件和硬件方面的特性,例如:“K60 至尊版,双芯旗舰
  • 线程通讯的三种方法!通俗易懂

    线程通信是指多个线程之间通过某种机制进行协调和交互,例如,线程等待和通知机制就是线程通讯的主要手段之一。 在 Java 中,线程等待和通知的实现手段有以下几种方式:Object 类下
  • 一文看懂为苹果Vision Pro开发应用程序

    译者 | 布加迪审校 | 重楼苹果的Vision Pro是一款混合现实(MR)头戴设备。Vision Pro结合了虚拟现实(VR)和增强现实(AR)的沉浸感。其高分辨率显示屏、先进的传感器和强大的处理能力
  • 如何正确使用:Has和:Nth-Last-Child

    我们可以用CSS检查,以了解一组元素的数量是否小于或等于一个数字。例如,一个拥有三个或更多子项的grid。你可能会想,为什么需要这样做呢?在某些情况下,一个组件或一个布局可能会
  • 虚拟键盘 API 的妙用

    你是否在遇到过这样的问题:移动设备上有一个固定元素,当激活虚拟键盘时,该元素被隐藏在了键盘下方?多年来,这一直是 Web 上的默认行为,在本文中,我们将探讨这个问题、为什么会发生
  • 零售大模型“干中学”,攀爬数字化珠峰

    文/侯煜编辑/cc来源/华尔街科技眼对于绝大多数登山爱好者而言,攀爬珠穆朗玛峰可谓终极目标。攀登珠峰的商业路线有两条,一是尼泊尔境内的南坡路线,一是中国境内的北坡路线。相
  • 一条抖音4亿人围观 ! 这家MCN比无忧传媒还野

    作者:Hiu 来源:互联网品牌官01 擦边少女空降热搜,幕后推手曝光被网友誉为&ldquo;纯欲天花板&rdquo;的女网红井川里予,近期因为一组哥特风照片登上热搜,引发了一场互联网世界关于
  • 小米MIX Fold 3下月亮相:今年唯一无短板的全能折叠屏

    这段时间以来,包括三星、一加、荣耀等等有不少品牌旗下的最新折叠屏旗舰都有新的进展,其中荣耀、三星都已陆续发布了最新的折叠屏旗舰,尤其号荣耀Magi
  • 外交部:美方应停止在网络安全问题上不负责任地指责他国

      中国外交部今天(16日)举行例行记者会。会上,有记者问,美国情报官员称,他们正在阻拦来自中国以及其他国家的黑客获取相关科研成果。 中方对此有何评论?对此
Top