我有两个异步函数,分别称为ChangeState()
和DoThing()
。它们每个都等待下游异步方法。这些是从事件处理程序调用的,因此它们在执行时不会阻塞任何其他代码。如果调用ChangeState()
,则必须先完成DoThing()
的操作,然后ChangeState()
才能执行此操作。 ChangeState()
仍在执行时可以再次调用。在DoThing()
之前开始的所有执行都应在DoThing()
继续之前完成。
反之亦然; ChangeState()
应该等待,直到之前运行的DoStuff()
完成为止。
如何在没有死锁危险的情况下实现这一目标?
我知道在锁语句中不允许等待,这是有充分的理由的,这就是为什么我不尝试重新创建该功能的原因。
async void ChangeState(bool state)
{
//Wait here until any pending DoStuff() is complete.
await OutsideApi.ChangeState(state);
}
async void DoStuff()
{
//Wait here until any pending ChangeState() is complete.
await OutsideApi.DoStuff();
}
答案 0 :(得分:2)
根据您的要求,似乎ReaderWriterLock
可以为您提供帮助。另外,由于您有async
个方法,因此应使用async
锁。不幸的是,.NET框架本身不提供等待等待的ReaderWriterLock
锁定。幸运的是,您可以看看AsyncEx库或这个article。使用AsyncEx
的示例。
var readerWriterLock = new AsyncReaderWriterLock();
async void ChangeState(bool state)
{
using(await readerWriterLock.ReaderLockAsync())
{
await OutsideApi.ChangeState(state);
}
}
async void DoStuff()
{
using(await readerWriterLock.WriterLockAsync())
{
await OutsideApi.DoStuff();
}
}
nb 该解决方案仍然存在以下局限性:DoStuff
调用不能是并发的,但写锁是锁定的,但是调用的顺序仍然是必须完成所有DoStuff
ChangeState
之前,反之亦然。(@ Scott Chamberlain给出的提示同时使用读写器锁)
答案 1 :(得分:0)
编辑:第一个解决方案不符合要求。
public class CustomLock
{
private readonly int[] Running;
private readonly object _lock;
public CustomLock(int Count)
{
Running = new int[Count];
_lock = new object();
}
public void LockOne(int Task)
{
lock (_lock)
{
Running[Task]++;
}
}
public void UnlockOne(int Task)
{
lock (_lock)
{
Running[Task]--;
}
}
public bool Locked(int Task)
{
lock (_lock)
{
for (int i = 0; i < Running.Length; i++)
{
if (i != Task && Running[i] != 0)
return true;
}
return false;
}
}
}
private CustomLock Lock = new CustomLock(2); //Create a new instance of the class for 2 tasks
async Task ChangeState(bool state)
{
while (Lock.Locked(0)) //Wait for the task to get unlocked
await Task.Delay(10);
Lock.LockOne(0); //Lock this task
await OutsideApi.ChangeState(state);
Lock.UnlockOne(0); //Task finished, unlock one
}
async Task DoStuff()
{
while (Lock.Locked(1))
await Task.Delay(10);
Lock.LockOne(1);
await OutsideApi.DoStuff();
Lock.UnlockOne(1);
}
虽然任何ChangeState正在运行,但无需等待就可以启动新的ChangeState,但是当调用DoStuff时,它将等待直到所有ChangeState都完成,而这也相反。
答案 2 :(得分:0)
答案 3 :(得分:0)
我为练习制作了一个名为KeyedLock
的同步原语,该原语一次只允许一个键的并发异步操作。所有其他密钥都已排队,以后(按密钥)分批解锁。该类旨在像这样使用:
KeyedLock _keyedLock;
async Task ChangeState(bool state)
{
using (await this._keyedLock.LockAsync("ChangeState"))
{
await OutsideApi.ChangeState(state);
}
}
async Task DoStuff()
{
using (await this._keyedLock.LockAsync("DoStuff"))
{
await OutsideApi.DoStuff();
}
}
例如下面的通话:
await ChangeState(true);
await DoStuff();
await DoStuff();
await ChangeState(false);
await DoStuff();
await ChangeState(true);
...将按以下顺序执行:
ChangeState(true);
ChangeState(false); // concurrently with the above
ChangeState(true); // concurrently with the above
DoStuff(); // after completion of the above
DoStuff(); // concurrently with the above
DoStuff(); // concurrently with the above
KeyedLock
类:
class KeyedLock
{
private object _currentKey;
private int _currentCount = 0;
private WaitingQueue _waitingQueue = new WaitingQueue();
private readonly object _locker = new object();
public Task WaitAsync(object key, CancellationToken cancellationToken)
{
if (key == null) throw new ArgumentNullException(nameof(key));
lock (_locker)
{
if (_currentKey != null && key != _currentKey)
{
var waiter = new TaskCompletionSource<bool>();
_waitingQueue.Enqueue(new KeyValuePair<object,
TaskCompletionSource<bool>>(key, waiter));
if (cancellationToken != null)
{
cancellationToken.Register(() => waiter.TrySetCanceled());
}
return waiter.Task;
}
else
{
_currentKey = key;
_currentCount++;
return cancellationToken.IsCancellationRequested ?
Task.FromCanceled(cancellationToken) : Task.FromResult(true);
}
}
}
public Task WaitAsync(object key) => WaitAsync(key, CancellationToken.None);
public void Release()
{
List<TaskCompletionSource<bool>> tasksToRelease;
lock (_locker)
{
if (_currentCount <= 0) throw new InvalidOperationException();
_currentCount--;
if (_currentCount > 0) return;
_currentKey = null;
if (_waitingQueue.Count == 0) return;
var newWaitingQueue = new WaitingQueue();
tasksToRelease = new List<TaskCompletionSource<bool>>();
foreach (var entry in _waitingQueue)
{
if (_currentKey == null || entry.Key == _currentKey)
{
_currentKey = entry.Key;
_currentCount++;
tasksToRelease.Add(entry.Value);
}
else
{
newWaitingQueue.Enqueue(entry);
}
}
_waitingQueue = newWaitingQueue;
}
foreach (var item in tasksToRelease)
{
item.TrySetResult(true);
}
}
private class WaitingQueue :
Queue<KeyValuePair<object, TaskCompletionSource<bool>>>
{ }
public Task<Releaser> LockAsync(object key,
CancellationToken cancellationToken)
{
var waitTask = this.WaitAsync(key, cancellationToken);
return waitTask.ContinueWith(
(_, state) => new Releaser((KeyedLock)state),
this, cancellationToken,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default
);
}
public Task<Releaser> LockAsync(object key)
=> LockAsync(key, CancellationToken.None);
public struct Releaser : IDisposable
{
private readonly KeyedLock _parent;
internal Releaser(KeyedLock parent) { _parent = parent; }
public void Dispose() { _parent?.Release(); }
}
}
答案 4 :(得分:-3)
这似乎很适合一对 Doctors doctor = new Doctors(name, surname, title, licenseNumber, phone,
email, nationality, speciality, LocalDate.parse(dateOfBirth),
Boolean.valueOf(isATeacher));
。
ReaderWriterLockSlim
一个控制对private readonly ReaderWriterLockSlim changeStateLock = new ReaderWriterLockSlim();
private readonly ReaderWriterLockSlim doStuffLock = new ReaderWriterLockSlim();
的访问,另一个控制对ChangeState
的访问。
读取器锁用于指示正在执行一种方法,写入器锁用于指示正在执行另一种方法。 DoStuff
允许多次读取,但写入是排他的。
ReaderWriterLockSlim
只是将控制权交还给调用者,因为Task.Yield
的ar阻塞。
ReaderWriterLockSlim