如果程序用到了并发技术,那就要特别留意这种情况:一段代码需要修改数据,同时其他代码需要访问同一个数据。
这种情况就需要考虑同步地访问数据。
如果下面三个条件都满足,就必须用同步来保护共享的数据。
- 多段代码正在并发运行;
- 这几段代码在访问(读或写)同一个数据;
- 至少有一段代码在修改(写)数据。
一 阻塞锁
如果有多个线程需要安全地读写共享数据,这种情况可以考虑使用lock语句。
一个线程进入锁后,在锁被释放之前其他线程是无法进入的:- class MyClass
- {
- private readonly object _mutex = new object();
- private int _value;
- public void Increment()
- {
- lock (_mutex)
- {
- _value = _value + 1;
- }
- }
- }
复制代码 .NET 框 架 中 还 有 很 多 其 他 类 型 的 锁, 如 Monitor、SpinLock、ReaderWriterLockSlim。
关于lock的使用,有四条重要的准则:
- 限制锁的作用范围:
要尽量限制锁的作用范围。应该把 lock 语句使用的对象设为私有成员,并且永远不要暴露给非本类的方法。
每个类型通常最多只有一个锁。如果一个类型有多个锁,可考虑通过重构把它分拆成多个独立的类型。
lock可以锁定任何引用类型,但是建议为 lock 语句定义一个专用的成员,就像上例中那样。
- 文档中明确锁保护的内容;
- 锁范围内的代码尽量少:在锁定时执行的代码要尽可能得少。要特别小心阻塞调用。在锁定时不要做任何阻塞操作。
- 在控制锁的时候绝不运行随意的代码:
在锁定时绝不要调用随意的代码。随意的代码包括引发事件、调用虚拟方法、调用委托。
如果一定要运行随意的代码,就在释放锁之后运行。
二 异步锁
如果多个代码块需要安全地读写共享数据,并且这些代码块可能使用 await 语句,可以考虑使用SemaphoreSlim。- class MyClass1
- {
- private readonly SemaphoreSlim _mutex = new SemaphoreSlim(1);
- private int _value;
- public async Task DelayAndIncrementAsync()
- {
- await _mutex.WaitAsync();
- try
- {
- var oldValue = _value;
- await Task.Delay(TimeSpan.FromSeconds(oldValue));
- _value = oldValue + 1;
- }
- finally
- {
- _mutex.Release();
- }
- }
- }
复制代码
三 阻塞信号
如果需要从一个线程发送信号给另一个线程,可以考虑使用ManualResetEventSlim。
一个ManualResetEventSlim的对象处于这两种状态其中之一:标记的(signaled)或未标记的(unsignaled)。
每个线程都可以把事件设置为signaled 状态,也可以把它重置为 unsignaled 状态。线程也可等待事件变为 signaled 状态。
下面的两个方法被两个独立的线程调用,一个线程等待另一个线程的信号:- class MyClass2
- {
- private readonly ManualResetEventSlim _mres = new ManualResetEventSlim();
- private int _value;
- public int WaitForInitialization()
- {
- _mres.Wait();
- return _value;
- }
- public void InitializeFromAnotherThread()
- {
- _value = 13;
- _mres.Set();
- }
- }
复制代码 在 .NET 框架中,还有一些线程同步信号类型。ManualResetEvent、AutoResetEvent、CountdownEvent。
四 异步信号
需要在代码的各个部分间发送通知,并且要求接收方必须进行异步等待。
如果该通知只需要发送一次,那可用 TaskCompletionSource 异步发送。
发送代码调用TrySetResult,接收代码等待它的 Task 属性:- class MyClass3
- {
- private readonly TaskCompletionSource<object> _tcs = new TaskCompletionSource<object>();
- private int _value1;
- private int _value2;
- public async Task<int> WaitForInitializationAsync()
- {
- await _tcs.Task;
- return _value1 + _value2;
- }
- public void Initialize()
- {
- _value1 = 13;
- _value2 = 17;
- _tcs.TrySetResult(null);
- }
- }
复制代码
以上。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |