C#线程化-以线程安全的方式使用类,而不是将其实现为线程安全
本文关键字:线程 安全 实现 方式使 | 更新日期: 2023-09-27 18:08:41
假设我想使用.Net Framework
中的一个非线程安全类(文档指出它不是线程安全的(。有时我从一个线程更改Property X
的值,有时从另一个线程,但我从不同时从两个线程访问它。有时我从一个线程调用Method Y
,有时从另一个线程,但从不同时调用。
这是否意味着我以线程安全的方式使用该类,以及文档声明它不是线程安全的不再与我的情况相关?
如果答案是否定的:我可以在同一个线程中完成与特定对象相关的所有操作吗?即,创建它并始终在同一线程中调用其成员(但不能在GUI线程中(?如果是,我该怎么做?(如果相关,它是一个WPF应用程序(。
不,它不是线程安全的。一般来说,如果没有某种同步,就不应该编写多线程代码。在您的第一个示例中,即使您设法确保修改/读取永远不会同时完成,仍然存在缓存值和指令重新排序的问题。
例如,CPU将值缓存到寄存器中,在一个线程上更新它,从另一个线程读取它。如果第二个缓存了它,它就不会去RAM获取它,也看不到更新后的值。
看看这篇伟大的文章,了解更多关于编写无锁多线程代码链接的信息和问题。它很好地解释了CPU、编译器和CLI字节码编译器如何对指令进行重新排序。
假设我想使用.Net Framework中的一个非线程安全类(文档中指出它不是线程安全的(。
"线程安全"有许多不同的含义。大多数物体分为三类:
- 线程仿射。这些对象只能从单个线程访问,而不能从其他线程访问。大多数UI组件都属于这一类
- 线程安全。这些对象可以在任何时间从任何线程访问。大多数同步对象(包括并发集合(都属于这一类
- 一次一个。这些对象可以一次从一个线程访问。这是"默认"类别,大多数.NET类型都属于这一类别
有时我会从一个线程更改属性X的值,有时也会从另一个线程中更改,但我从不同时从两个线程访问它。有时我从一个线程调用方法Y,有时从另一个线程中调用,但从不同时调用。
正如另一位回答者所指出的,您必须考虑指令的重新排序和缓存读取。换句话说,仅仅在不同的时间进行这些操作是不够的;您需要实施适当的屏障,以确保其正确工作。
最简单的方法是使用lock
语句保护对象的所有访问。如果所有的读取、写入和方法调用都在同一个锁内,那么这将起作用(假设对象确实有一种一次一个的线程模型,而不是线程仿射(。
假设我想使用.Net Framework中的一个非线程安全类(文档中指出它不是线程安全的(。有时我会从一个线程更改属性X的值,有时也会从另一个线程中更改,但我从不同时从两个线程访问它。有时我从一个线程调用方法Y,有时从另一个线程中调用,但从不同时调用。
默认情况下,所有Classes
都是非线程安全的,只有少数Collections
(如专门为thread safety
设计的Concurrent Collections
(除外。因此,对于您可以选择的任何其他类,如果您通过multiple threads
或以Non atomic
的方式访问它,无论是read / write
,那么在更改对象的状态时都必须引入线程安全性。这只适用于其状态可以在多线程环境中修改的对象,但方法本身只是功能实现,它们本身不是一个可以修改的状态,它们只是引入了线程安全来维护对象状态。
这是否意味着我以线程安全的方式使用该类,而文档中声明它不线程安全的事实与我的情况不再相关?如果答案是否定的:我可以在同一个线程(而不是GUI线程(中完成与类相关的所有操作吗?如果是,我该怎么做?(如果相关,它是一个WPF应用程序(。
对于Ui应用程序,考虑将Async-Await
引入基于IO的操作,如文件读取、数据库读取,并将TPL
用于计算绑定操作。Async-Await
的优点在于:
- 它根本不阻塞Ui线程,并保持Ui完全响应,事实上,
await
后的Ui控件可以直接更新,而不需要跨线程,因为只涉及一个线程 - TPL并发性也使计算操作阻塞,它们从线程池中调用线程,并且由于跨线程问题而不能用于Ui更新
最后:有些类中,一个方法启动操作,另一个方法结束操作。例如,使用SpeechRecognitionEngine类,您可以使用RecognizeAsync启动语音识别会话(此方法在TPL库之前,因此不会返回Task(,然后使用Recognize AsyncCancel取消识别会话。如果我从一个线程中调用RecognizeAsync,从另一个线程调用Recognize AsyncCancel,该怎么办?(它有效,但它"安全"吗?它会在我不知道的某些情况下失败吗?(
正如您所提到的Async
方法,这可能是一个基于APM
的旧实现,它需要AsyncCallBack
来协调,类似于BeginXX, EndXX
,如果是这样的话,那么不需要太多协调,因为它们使用AsyncCallBack来执行回调委托。事实上,正如前面提到的,这里没有涉及额外的线程,无论是旧版本还是新的Async-Await
。关于任务取消,CancellationTokenSource
可以用于Async-Await
,不需要单独的取消任务。多线程之间的协调可以通过自动/手动重置事件来完成。如果上面提到的调用是同步的,那么使用Task包装器返回Task。可以通过Async方法调用它们,如下所示:
await Task.Run(() => RecognizeAsync())
虽然它是一种反模式,但可以用于制作整个呼叫链Async
编辑(回答OP问题(
谢谢你的详细回答,但我不明白其中的一些。在第一点上,你说"引入线程安全是必要的",但怎么做呢?
- 线程安全是使用类似
lock, mutex, semaphore, monitor, Interlocked,
的同步结构引入的,所有这些结构的目的都是为了防止对象出现损坏/竞争条件。我看不到任何台阶
正如我在帖子中所描述的,我所采取的步骤是否足够?
- 我在你的帖子中没有看到任何线程安全步骤,请突出显示你正在谈论的步骤
在第二点上,我问如何一直在同一个线程中使用对象(无论何时使用(。异步Await与此无关,AFAIK。
- Async Await是并发中唯一的机制,因为它除了调用线程之外不涉及任何额外的线程,所以可以确保所有东西都在同一个线程上运行,因为它使用
IO completion ports
(基于硬件的并发(,否则如果使用Task Parallel library
,则无法确保始终使用相同/给定的线程,因为这是一个非常高级的抽象
在这里查看我最近关于线程的一个详细答案,它可能有助于提供一些更详细的方面
它不是线程安全的,因为存在技术风险,但您的策略旨在处理问题并解决风险。所以,如果事情如你所描述的那样,那么你就没有线程安全的环境,然而,你是安全的。现在。