使用 IDisposable 模式在 C# 中同步对资源的访问的方法
Posted
技术标签:
【中文标题】使用 IDisposable 模式在 C# 中同步对资源的访问的方法【英文标题】:Approach for synchronizing access to a resource in C# using the IDisposable pattern 【发布时间】:2021-06-17 00:19:06 【问题描述】:我正在考虑一种使用 IDisposable 模式来同步/协调对共享资源的访问的方法。
到目前为止,这是我的代码(易于使用 LinqPad 运行):
#define WITH_CONSOLE_LOG
//better undefine WITH_CONSOLE_LOG when testing long loops
public abstract class SynchronizedAccessBase
private readonly object syncObj = new();
private class AccessToken : IDisposable
private SynchronizedAccessBase parent;
private bool didDispose;
public AccessToken(SynchronizedAccessBase parent)
this.parent = parent;
protected virtual void Dispose(bool disposing)
if (!this.didDispose)
Monitor.Exit(this.parent.syncObj);
#if WITH_CONSOLE_LOG
Console.WriteLine("Monitor.Exit by Thread=" + Thread.CurrentThread.ManagedThreadId);
#endif
if (disposing)
//nothing specific here
this.didDispose = true;
~AccessToken()
this.Dispose(disposing: false);
void IDisposable.Dispose()
Dispose(disposing: true);
GC.SuppressFinalize(this);
public IDisposable WantAccess()
Monitor.Enter(this.syncObj);
#if WITH_CONSOLE_LOG
Console.WriteLine("Monitor.Enter by Thread=" + Thread.CurrentThread.ManagedThreadId);
#endif
return new AccessToken(this);
public class MyResource : SynchronizedAccessBase
public int Value;
private MyResource TheResource;
private void MyMethod()
using var token = TheResource.WantAccess(); //comment out this line to see the unsynced behavior
#if WITH_CONSOLE_LOG
Console.WriteLine("Inc'ing Value by Thread=" + Thread.CurrentThread.ManagedThreadId);
#endif
TheResource.Value++;
void Main()
this.TheResource = new MyResource();
var inc_tasks = new Task[10];
for (int i = 0; i < inc_tasks.Length; i++)
inc_tasks[i] = Task.Run(() =>
for (int loop = 1; loop <= 100; loop++)
MyMethod();
);
Task.WaitAll(inc_tasks);
Console.WriteLine("End of Main() with Value==" + TheResource.Value);
我想要实现的是在(独占)访问共享资源之前在方法顶部(或中间某处,谁在乎)使用 C#“使用”语句,并具有 IDisposable 机制自动结束独占访问。
在后台,Monitor 类用于此目的。
理想的优势是不需要缩进的 代码块 。只是一个 using... 行,就是这样。请参阅上面示例中的 MyMethod()。
这似乎工作得很好。所有公司的最终结果都符合预期,即使循环很长,如果我从 MyMethod 中删除 using.. 语句也是错误的。
但是,您认为我可以信任此解决方案吗? .Dispose 是否真的,真的总是在离开 MyMethod 时调用,即使出现异常?其他陷阱?
谢谢!
【问题讨论】:
是的,这是一种有效的方法。您可以看到类似使用 here 的示例(Releaser
类)。对于同步锁定,使用结构而不是类作为Releaser
的类型可能会更有效。
我尝试使用 struct 而不是 AccessToken 的类。是的,这明显更快(30-40%)
如果您决定将其设为结构,则应考虑将WantAccess
方法的返回类型从IDisposable
更改为AccessToken
(需要将AccessToken
设为公开)。这样该结构就不会被using
语句装箱,从而减少分配和垃圾收集器的工作。
@TheodorZoulias,我也试过了,但我无法衡量性能提升
也许protected virtual
、~AccessToken()
终结器和GC.SuppressFinalize(this);
调用会减慢速度。你真的需要这些东西吗?您是否预计必须从 AccessToken
类型继承的情况?
【参考方案1】:
如果在您进入 WantAccess 中的监视器之后并且在将返回值分配给 using
块中的变量之前引发异常,您的代码可能会在临界区范围之外持有锁。 lock
由于转换的方式,不可能做同样的事情。
lock
是专门为这个问题而设计的,并且经过非常精细的调整,可以在解决这类问题时完全按照您的意愿去做。您应该为工作使用正确的工具。
【讨论】:
(如果抛出异常则保持锁定)是的,这是一个好地方,而且那种错误是重复和发现的噩梦。你可以通过一些异常处理来解决这个问题。 @Servy 我明白你的意思。但是,我想不出为什么在成功调用 Monitor.Enter() 并且在将 WantAccess 的结果分配给 token 变量之前会发生这种异常的原因。好的,new AccessToken() 上可能会发生内存不足异常。因此,可以在 Monitor.Enter() 调用之前更好地准备该实例。其他想法可能会出什么问题(在现实世界中)? @mikelegg 遗憾的是你不能。你可以处理一些情况,但不是全部。lock
的作用是在 try/finally 中调用 Monitor.TryEnter,以便最终检查是否已获取锁并释放它,您需要复制它以涵盖它所做的所有情况。没有using
实现能够复制它。
@KarloX 好吧,它只需要一个,你提到了一个,所以这足以成为一个破坏者。最重要的是,您可能会出现堆栈溢出和线程中止异常。最后,您正在访问标准输出,这可能由于多种原因而引发。
@Servy,好的标准输出当然不会变成生产代码。线程中止是一个有效点。仅返回结果并将其分配给声明的局部变量时的堆栈溢出,嗯,我不知道,也许。谢谢【参考方案2】:
我认为这没有任何问题。我之前以类似的方式使用了 using 模式,没有问题。
作为一个简单的例子,考虑使用数据库连接的模式。下面有一个连接池,从池中创建一个新连接(可能正在等待)并将释放释放回池。这和你的一样,都是 1 个项目。
如果资源仅在 1 个进程内共享,我不知道仅使用简单的 lock() 模式是否值得。那会更“传统”。但只有你能回答。
【讨论】:
以上是关于使用 IDisposable 模式在 C# 中同步对资源的访问的方法的主要内容,如果未能解决你的问题,请参考以下文章