阻止两个异步方法同时运行

时间:2019-04-13 08:30:32

标签: c# .net multithreading concurrency async-await

我有两个异步函数,分别称为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();
}

5 个答案:

答案 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)

编辑:第一个解决方案不符合要求。

  1. 创建自定义锁类。
    此类跟踪哪种类型(ChangeState和DoThing)正在运行多少实例,并提供一种检查任务是否可以运行的方法。
        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;
                }
            }
        }
  1. 更改现有代码。
    ChangeState将是任务0,而DoStuff将是任务1。
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)

您可以使用ManualResetEvent或AutoResetEvent发出信号,表明一个线程已完成,以便另一个线程可以继续工作。

可以找到herehere的一些示例:

答案 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