使用 IDisposable 模式在 C# 中同步对资源的访问的方法
Approach for synchronizing access to a resource in C# using the IDisposable pattern
我正在考虑使用 IDisposable 模式 synchonize/coordinate 访问共享资源的方法。
到目前为止,这是我的代码(使用 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#“using”语句并具有IDisposable机制自动结束独占访问
在后台,监视器 class 用于此目的。
理想的优点是不需要缩进的{代码块}。只是一条 using... 行,仅此而已。请参阅上面示例中的 MyMethod()。
这似乎工作得很好。所有 inc 的最终结果都符合预期,即使循环很长,如果我从 MyMethod 中删除 using.. 语句也是错误的。
但是,您认为我可以信任这个解决方案吗? .Dispose of the token 真的,真的总是在离开 MyMethod 时被调用,即使是在出现异常的情况下?其他陷阱?
谢谢!
我完全看不出这有什么不妥。我之前以类似的方式使用过 using 模式,没有问题。
举一个简单的例子,考虑使用数据库连接的模式。下面有一个连接池,创建一个新的连接从池中获取(可能等待)并将释放返回池中。这与您拥有的一样,都是 1 个项目的池。
我不知道如果资源仅在 1 个进程内共享,那么仅使用简单的 lock(){} 模式是否值得付出努力。那会更多'conventional'。但只有你能回答。
如果在 WantAccess 中进入监视器后且在将返回值分配给 [=10 中的变量之前抛出异常,则您的代码有可能持有临界区范围之外的锁=] 块。 lock
由于转换的完成方式,没有可能做同样的事情。
lock
正是专门为这个问题设计的,并且经过精心调整,可以在解决此类问题时完全按照您的意愿去做。您应该使用正确的工具来完成这项工作。
我正在考虑使用 IDisposable 模式 synchonize/coordinate 访问共享资源的方法。
到目前为止,这是我的代码(使用 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#“using”语句并具有IDisposable机制自动结束独占访问
在后台,监视器 class 用于此目的。
理想的优点是不需要缩进的{代码块}。只是一条 using... 行,仅此而已。请参阅上面示例中的 MyMethod()。
这似乎工作得很好。所有 inc 的最终结果都符合预期,即使循环很长,如果我从 MyMethod 中删除 using.. 语句也是错误的。
但是,您认为我可以信任这个解决方案吗? .Dispose of the token 真的,真的总是在离开 MyMethod 时被调用,即使是在出现异常的情况下?其他陷阱?
谢谢!
我完全看不出这有什么不妥。我之前以类似的方式使用过 using 模式,没有问题。
举一个简单的例子,考虑使用数据库连接的模式。下面有一个连接池,创建一个新的连接从池中获取(可能等待)并将释放返回池中。这与您拥有的一样,都是 1 个项目的池。
我不知道如果资源仅在 1 个进程内共享,那么仅使用简单的 lock(){} 模式是否值得付出努力。那会更多'conventional'。但只有你能回答。
如果在 WantAccess 中进入监视器后且在将返回值分配给 [=10 中的变量之前抛出异常,则您的代码有可能持有临界区范围之外的锁=] 块。 lock
由于转换的完成方式,没有可能做同样的事情。
lock
正是专门为这个问题设计的,并且经过精心调整,可以在解决此类问题时完全按照您的意愿去做。您应该使用正确的工具来完成这项工作。