如何保护可能在多线程或异步环境中使用的资源?
Posted
技术标签:
【中文标题】如何保护可能在多线程或异步环境中使用的资源?【英文标题】:How to protect resources that may be used in a multi-threaded or async environment? 【发布时间】:2014-01-27 11:17:01 【问题描述】:我正在开发一个可供各种消费者使用的 C# API。此 API 提供对共享资源(在我的情况下是进行串行通信的硬件)的访问,通常会有几个不同的参与者尝试同时使用它。
我遇到的问题是我的一些消费者希望在多线程环境中使用它——每个参与者独立工作并尝试使用资源。一个简单的锁在这里工作正常。但是我的一些消费者更喜欢使用 async-await 并对资源进行时间切片。 (据我了解)这需要一个异步锁来将时间片返回给其他任务;在锁处阻塞会停止整个线程。
我认为拥有串行锁充其量是性能不佳,最坏的情况是潜在的竞争条件或死锁。
那么我怎样才能在一个公共代码库中保护这个共享资源,以供两种潜在的并发使用呢?
【问题讨论】:
【参考方案1】:您可以使用 SemaphoreSlim
和 1 作为请求数。 SemaphoreSlim
允许使用 WaitAsync
和旧的同步方式以 async
方式锁定:
await _semphore.WaitAsync()
try
... use shared resource.
finally
_semphore.Release()
您也可以根据 Stephen Toub 的精彩帖子 Building Async Coordination Primitives, Part 6: AsyncLock 编写自己的 AsyncLock
。我在我的应用程序中做到了这一点,并允许在同一个构造上同时使用同步和异步锁。
用法:
// Async
using (await _asyncLock.LockAsync())
... use shared resource.
// Synchronous
using (_asyncLock.Lock())
... use shared resource.
实施:
class AsyncLock
private readonly Task<IDisposable> _releaserTask;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly IDisposable _releaser;
public AsyncLock()
_releaser = new Releaser(_semaphore);
_releaserTask = Task.FromResult(_releaser);
public IDisposable Lock()
_semaphore.Wait();
return _releaser;
public Task<IDisposable> LockAsync()
var waitTask = _semaphore.WaitAsync();
return waitTask.IsCompleted
? _releaserTask
: waitTask.ContinueWith(
(_, releaser) => (IDisposable) releaser,
_releaser,
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
private class Releaser : IDisposable
private readonly SemaphoreSlim _semaphore;
public Releaser(SemaphoreSlim semaphore)
_semaphore = semaphore;
public void Dispose()
_semaphore.Release();
【讨论】:
这看起来并不能解决我的问题。这提供了相同的机制,但仍会强制我的 API 在异步锁和同步锁之间进行选择 - 当它必然无法知道两者中的哪一个正在调用它时。除非我误会了? @Telastyn:您的 API 方法是同步的或异步的。如果它们是同步的,请使用Wait
;如果它们是异步的,请使用WaitAsync
。
@Telastyn:做async
的正确方法是做async
“一路”。是的,这意味着如果您确定既要异步又要同步,那么您最终会得到并行路径。有一些技巧可以通过异步进行异步或通过异步进行同步,但是它们都不能完美地工作或在所有情况下都可以工作。
好的,我做了一些实验,看起来异步锁应该做我需要的,因为即使在多线程场景中它也会保护共享资源。延续(似乎)只是在那个单独的线程中运行,提供正确的行为。并且资源和 API 边界之间的中间层可能会出现非异步。
补充@StephenCleary 所说的,在调用堆栈中交织同步和异步会很快导致有效的死锁。很多时候异步任务被安排在同一个线程上,如果在两个异步调用之间混合了同步阻塞调用,父异步调用可能会无限期阻塞,等待子异步调用,因为同步阻塞调用永远无法完成.因为它们都在同一个线程上运行,所以所有工作都停止了。或类似的东西。我曾经发生过这种情况,我发誓再也不会这样做了。以上是关于如何保护可能在多线程或异步环境中使用的资源?的主要内容,如果未能解决你的问题,请参考以下文章