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

时间:2016-09-24 12:41:19

标签: c# .net multithreading thread-safety

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

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

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

4 个答案:

答案 0 :(得分:3)

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

例如,CPU将值缓存到寄存器中,您在一个线程上更新它,从另一个线程读取它。如果第二个缓存了它,它就不会进入RAM来获取它并且没有看到更新的值。

有关编写无锁多线程代码link的更多信息和问题,请查看这篇精彩文章。它有一个很好的解释,CPU,编译器和CLI字节码编译器如何重新排序指令。

答案 1 :(得分:2)

  

假设我想使用.Net Framework中的非线程安全类(文档声明它不是线程安全的)。

“线程安全”有许多不同的含义。大多数对象分为三类:

  • 线程仿射。这些对象只能从单个线程访问,而不能从另一个线程访问。大多数UI组件属于此类别。
  • 线程安全。可以随时从任何线程访问这些对象。大多数同步对象(包括并发集合)属于此类别。
  • 一在-A-时间。可以一次从一个线程访问这些对象。这是“默认”类别,大多数.NET类型属于此类别。
  

有时我从一个线程更改属性X的值,有时从另一个线程更改,但我从不同时从两个线程访问它。有时我从一个线程调用Method Y,有时从另一个线程调用,但从不同时调用。

正如另一位回答者所指出的,你必须考虑指令重新排序和缓存读取。换句话说,仅仅在不同时间执行这些操作是不够的;你需要实施适当的障碍,以确保它能够正常工作。

最简单的方法是使用lock语句保护对象的所有访问权限。如果所有读取,写入和方法调用都在同一个锁中,那么这将起作用(假设对象确实具有一次一种的线程模型而不是线程仿射)。

答案 2 :(得分:1)

  

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

默认情况下,所有Classes都是非线程安全的,除了Collections之类的Concurrent Collections专门为thread safety设计的multiple threads。因此,对于您可以选择的任何其他课程,如果您通过Non atomicread / write方式访问它,无论Async-Await是否必须在更改时引入线程安全性对象的状态。这仅适用于可以在多线程环境中修改其状态的对象,但这样的方法只是功能实现,它们本身不是可以修改的状态,它们只是引入线程安全来维护对象状态。 / p>

  

这是否意味着我以线程安全的方式使用该类,并且文档声明它不是线程安全的事实不再与我的情况相关?如果答案是否:我可以在同一个线程(但不是GUI线程)中执行与类相关的所有操作吗?如果是这样,我该怎么做? (如果相关,它是一个WPF应用程序。)

对于Ui应用程序,请考虑为基于IO的操作引入TPL,例如文件读取,数据库读取和使用Async-Await进行计算绑定操作。 await的好处是:

  • 它根本没有阻止Ui线程,并且保持Ui完全响应,实际上post Async Ui控件可以直接更新而没有Cross线程关注,因为只涉及一个线程
  • TPL并发性也使得计算操作阻塞,它们从线程池中召唤线程,并且由于跨线程关注而无法用于Ui更新
  

最后:有一个类,其中一个方法启动一个操作,另一个方法结束它。例如,使用SpeechRecognitionEngine类,您可以使用RecognizeAsync启动语音识别会话(此方法在TPL库之前,因此它不返回任务),然后使用RecognizeAsyncCancel取消识别会话。如果我从一个线程调用RecognizeAsync并从另一个线程调用RecognizeAsyncCancel怎么办? (它有效,但它是否安全"?它会在某些我不知道的情况下失败吗?)

正如您提到的APM方法,这可能是一个较早的实现,基于AsyncCallBack,需要BeginXX, EndXX来协调,Async-Await行如果是这种情况,那么协调就不需要太多,因为他们使用AsyncCallBack来执行回调委托。实际上如前所述,这里没有涉及额外的线程,无论是旧版本还是新版本CancellationTokenSource。关于任务取消,Async-Await可以用于await Task.Run(() => RecognizeAsync()) ,不需要单独的取消任务。在多个线程之间,可以通过Auto / Manual ResetEvent进行协调。 如果上面提到的调用是同步的,那么使用Task包装器返回Task可以通过Async方法调用它们,如下所示:

Async

虽然它是一种反模式,但在制作整个调用链时很有用lock, mutex, semaphore, monitor, Interlocked,

编辑(回答OP问题)

  

感谢您的详细解答,但我对其中的一些内容并不了解。在第一点,你说"引入线程安全"是必要的,但是如何?

  1. 使用IO completion ports等同步构造引入线程安全,所有这些都用于保存对象免于损坏/竞争条件。我没有看到任何步骤。
  2.   

    我的帖子中描述的步骤是否足够?

    1. 我的帖子中没有看到任何主题安全步骤,请突出显示您正在谈论的步骤
    2.   

      在第二点,我一直在询问如何在同一个线程中使用对象(每当我使用它时)。 Async-Await与此无关,AFAIK。

      1. Async-Await是并发中唯一的机制,因为它不涉及调用线程旁边的任何额外线程,可以确保所有内容始终在同一个线程上运行,因为它使用Task Parallel library(基于硬件的并发) ),否则如果您使用{{1}},那么您无法确保始终使用相同/给定的线程,因为这是一个非常高级别的抽象
      2. 查看我最近关于线程here的详细答案之一,它可能有助于提供更详细的方面

答案 3 :(得分:0)

由于技术风险存在,它不是线程安全的,但您的策略旨在解决问题并解决风险。因此,如果事情符合您的描述,那么您就没有线程安全的环境,但是,您是安全的。现在。