C#线程化-以线程安全的方式使用类,而不是将其实现为线程安全

本文关键字:线程 安全 实现 方式使 | 更新日期: 2023-09-27 18:08:41

假设我想使用.Net Framework中的一个非线程安全类(文档指出它不是线程安全的(。有时我从一个线程更改Property X的值,有时从另一个线程,但我从不同时从两个线程访问它。有时我从一个线程调用Method Y,有时从另一个线程,但从不同时调用。

这是否意味着我以线程安全的方式使用该类,以及文档声明它不是线程安全的不再与我的情况相关?

如果答案是否定的:我可以在同一个线程中完成与特定对象相关的所有操作吗?即,创建它并始终在同一线程中调用其成员(但不能在GUI线程中(?如果是,我该怎么做?(如果相关,它是一个WPF应用程序(。

C#线程化-以线程安全的方式使用类,而不是将其实现为线程安全

不,它不是线程安全的。一般来说,如果没有某种同步,就不应该编写多线程代码。在您的第一个示例中,即使您设法确保修改/读取永远不会同时完成,仍然存在缓存值和指令重新排序的问题。

例如,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问题(

谢谢你的详细回答,但我不明白其中的一些。在第一点上,你说"引入线程安全是必要的",但怎么做呢?

  1. 线程安全是使用类似lock, mutex, semaphore, monitor, Interlocked,的同步结构引入的,所有这些结构的目的都是为了防止对象出现损坏/竞争条件。我看不到任何台阶

正如我在帖子中所描述的,我所采取的步骤是否足够?

  1. 我在你的帖子中没有看到任何线程安全步骤,请突出显示你正在谈论的步骤

在第二点上,我问如何一直在同一个线程中使用对象(无论何时使用(。异步Await与此无关,AFAIK。

  1. Async Await是并发中唯一的机制,因为它除了调用线程之外不涉及任何额外的线程,所以可以确保所有东西都在同一个线程上运行,因为它使用IO completion ports(基于硬件的并发(,否则如果使用Task Parallel library,则无法确保始终使用相同/给定的线程,因为这是一个非常高级的抽象

在这里查看我最近关于线程的一个详细答案,它可能有助于提供一些更详细的方面

它不是线程安全的,因为存在技术风险,但您的策略旨在处理问题并解决风险。所以,如果事情如你所描述的那样,那么你就没有线程安全的环境,然而,你是安全的。现在。