Насколько корректен этот AsyncLock?
От: IObserver Ниоткуда  
Дата: 27.09.12 06:57
Оценка:
В блоге нашел реализацию AsyncLock: http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx

Сделано не корректно, что видно из примера:


class Program
    {
        static readonly AsyncLock al = new AsyncLock();
        static bool called = false;

        static void Main(string[] args)
        {
            AsyncCaller();
            Console.ReadLine();
        }

        static async void AsyncCaller()
        {
            await DoItAsync();
            Console.WriteLine("Done"); // Программа не завершается
        }

        static async Task DoItAsync()
        {
            Console.WriteLine("before lock");

            using (await al.LockAsync())
            {
                Console.WriteLine("after lock"); // Не вызывается второй раз!!!

                if (!called)
                {
                    called = true;
                    await DoItAsync();
                }
            }
        }
    }


Согласны ли вы, что реализовано не корректно? И второй вопрос: где взять корректную реализацию?

На всякий случай приведу код этого AsyncLock, чтобы вам не лень было смотреть:

  Скрытый текст
class AsyncSemaphore
    {
        private readonly static Task s_completed = Task.FromResult(true);
        public readonly Queue<TaskCompletionSource<bool>> m_waiters = new Queue<TaskCompletionSource<bool>>();
        private int m_currentCount;

        public AsyncSemaphore(int initialCount)
        {
            if (initialCount < 0) throw new ArgumentOutOfRangeException("initialCount");
            m_currentCount = initialCount;
        }

        public Task WaitAsync()
        {
            lock (m_waiters)
            {
                if (m_currentCount > 0)
                {
                    --m_currentCount;
                    return s_completed;
                }
                else
                {
                    var waiter = new TaskCompletionSource<bool>();
                    m_waiters.Enqueue(waiter);
                    return waiter.Task;
                }
            }
        }

        public void Release()
        {
            TaskCompletionSource<bool> toRelease = null;
            lock (m_waiters)
            {
                if (m_waiters.Count > 0)
                    toRelease = m_waiters.Dequeue();
                else
                    ++m_currentCount;
            }
            if (toRelease != null)
                toRelease.SetResult(true);
        }
    }




class AsyncLock
    {
        public readonly AsyncSemaphore m_semaphore;
        private readonly Task<Releaser> m_releaser;

        public struct Releaser : IDisposable
        {
            private readonly AsyncLock m_toRelease;

            internal Releaser(AsyncLock toRelease) { m_toRelease = toRelease; }

            public void Dispose()
            {
                if (m_toRelease != null)
                    m_toRelease.m_semaphore.Release();
            }
        }

        public AsyncLock()
        {
            m_semaphore = new AsyncSemaphore(1);
            m_releaser = Task.FromResult(new Releaser(this));
        }

        public Task<Releaser> LockAsync()
        {
            var wait = m_semaphore.WaitAsync();
            return wait.IsCompleted ?
                m_releaser :
                wait.ContinueWith((_, state) => new Releaser((AsyncLock)state),
                    this, CancellationToken.None,
                    TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
        }
    }
Re: Насколько корректен этот AsyncLock?
От: samius Япония http://sams-tricks.blogspot.com
Дата: 27.09.12 07:21
Оценка: +1
Здравствуйте, IObserver, Вы писали:

IO>В блоге нашел реализацию AsyncLock: http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx


IO>Сделано не корректно, что видно из примера:


static void Main(string[] args)
{
    AsyncCaller();
    Thread.Sleep(1000);
    Console.ReadLine();
}

Воткните ка перед ReadLine слип, а то он походу блокирует консоль от вывода на нее WriteLine-ов из другого потока (на правах предположения).
Re[2]: Насколько корректен этот AsyncLock?
От: IObserver Ниоткуда  
Дата: 27.09.12 11:30
Оценка:
Здравствуйте, samius, Вы писали:

S>Воткните ка перед ReadLine слип, а то он походу блокирует консоль от вывода на нее WriteLine-ов из другого потока (на правах предположения).


Переписал с Debug.WriteLine -- разницы нет. Поток только один (см. код ниже).

class Program
    {
        static readonly AsyncLock al = new AsyncLock();
        static bool called = false;

        static void Main(string[] args)
        {
            AsyncCaller();
            Console.ReadLine();
        }

        static async void AsyncCaller()
        {
            await DoItAsync();
            Debug.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
            Debug.WriteLine("Done");
        }

        static async Task DoItAsync()
        {
            Debug.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
            Debug.WriteLine("before lock");

            using (await al.LockAsync())
            {
                Debug.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
                Debug.WriteLine("after lock"); // Не вызывается второй раз!!!

                if (!called)
                {
                    called = true;
                    await DoItAsync();
                }
            }
        }
    }
Re[3]: Насколько корректен этот AsyncLock?
От: samius Япония http://sams-tricks.blogspot.com
Дата: 27.09.12 11:52
Оценка:
Здравствуйте, IObserver, Вы писали:

IO>Здравствуйте, samius, Вы писали:


S>>Воткните ка перед ReadLine слип, а то он походу блокирует консоль от вывода на нее WriteLine-ов из другого потока (на правах предположения).


IO>Переписал с Debug.WriteLine -- разницы нет. Поток только один (см. код ниже).


Посмотрел код, все согласно AsyncSemaphore -у. Первый вызов WaitAsync пропускает, второй без релиза ставится в очередь. Никакой проверки, тот же поток или не тот же нет, т.е. поведение будет отличаться от Monitor.Enter.
Re[4]: Насколько корректен этот AsyncLock?
От: IObserver Ниоткуда  
Дата: 27.09.12 11:54
Оценка:
Здравствуйте, samius, Вы писали:

S>Посмотрел код, все согласно AsyncSemaphore -у. Первый вызов WaitAsync пропускает, второй без релиза ставится в очередь. Никакой проверки, тот же поток или не тот же нет, т.е. поведение будет отличаться от Monitor.Enter.


Вот и я о том же -- реализация не корректна. Хотя вроде один из MS-овцев писал -- неужели таких вещей не знает.

Где брать правильное или чем заменить?
Re[5]: Насколько корректен этот AsyncLock?
От: samius Япония http://sams-tricks.blogspot.com
Дата: 27.09.12 12:03
Оценка:
Здравствуйте, IObserver, Вы писали:

IO>Здравствуйте, samius, Вы писали:


S>>Посмотрел код, все согласно AsyncSemaphore -у. Первый вызов WaitAsync пропускает, второй без релиза ставится в очередь. Никакой проверки, тот же поток или не тот же нет, т.е. поведение будет отличаться от Monitor.Enter.


IO>Вот и я о том же -- реализация не корректна. Хотя вроде один из MS-овцев писал -- неужели таких вещей не знает.

Он же не продакшн писал, чисто идеи.

IO>Где брать правильное или чем заменить?

Правильное, боюсь, нетривиально. Нужно чем-то помечать "нити" выполнения и уметь узнавать их, с оглядкой на то что псевдонити будут выполняться различными потоками (последовательно). Т.е. всякие TLS для хранения метки идут лесом.
Получается что псевдонить должна получать какой-то идентификатор и передавать его в Lock, что бы там можно было сверить с тем, кто уже захватил ресурс. Этот идентификатор надо будет передавать во вложенные await методы.

Не очень красиво, но работать будет. Возможно стоит пересмотреть логику и хранить признак захвата где-то в клиентском коде (т.е. не в коде AsyncLocker-а).
Re: Насколько корректен этот AsyncLock?
От: Sinix  
Дата: 27.09.12 12:42
Оценка:
Здравствуйте, IObserver, Вы писали:

IO>В блоге нашел реализацию AsyncLock: http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx

IO>Сделано не корректно, что видно из примера:
Корректно, просто он не поддерживает reentrancy

В последнем комментарии:

One important point is that this class does not support reentrancy the way the Monitor class (or C# lock keyword) does. So in that respect it's more like an AsyncSemaphore that's fixed at concurrency level 1. Since this class returns an IDisposable releaser, I prefer this class to the last post's semaphore class.


В общем случае я бы поостерёгся использовать блокирующие примитивы вместе с тасками/async, очень уж легко нарваться на вечную блокировку. Тем более — позаимствовав наброски из блога, которые просто демонстрируют идею, и как-то не похожи на production-ready код
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.