programing

잠금 명령문 본문에서 '대기' 연산자를 사용할 수 없는 이유는 무엇입니까?

goodsources 2023. 5. 1. 21:03
반응형

잠금 명령문 본문에서 '대기' 연산자를 사용할 수 없는 이유는 무엇입니까?

awaitCC#(.)는 NET. 비동기 CTP)"("C")"(으)lock진술.

MSDN에서:

대기 식은 동기식, 쿼리 식, 예외 처리 문의 캐치 또는 최종 블록, 잠금블록 또는 안전하지 않은 컨텍스트에서 사용할 수 없습니다.

이것은 컴파일러 팀이 어떤 이유로 구현하기 어렵거나 불가능하다고 생각합니다.

다음 문장을 사용하여 해결 방법을 시도했습니다.

class Async
{
    public static async Task<IDisposable> Lock(object obj)
    {
        while (!Monitor.TryEnter(obj))
            await TaskEx.Yield();

        return new ExitDisposable(obj);
    }

    private class ExitDisposable : IDisposable
    {
        private readonly object obj;
        public ExitDisposable(object obj) { this.obj = obj; }
        public void Dispose() { Monitor.Exit(this.obj); }
    }
}

// example usage
using (await Async.Lock(padlock))
{
    await SomethingAsync();
}

그러나 이것은 예상대로 작동하지 않습니다.:Monitor.Exit에 내에ExitDisposable.Dispose다른 스레드가 잠금을 획득하려고 시도할 때 교착 상태를 야기하는 경우가 대부분입니다.는 제 의 일에 과 그 를 의심합니다.await문허용 않다니습지에서는 .lock진술은 어떻게든 관련이 있습니다.

왜 그런지 아는 사람? await의 본문 내에서는 허용되지 않습니다.lock진술서?

이것은 컴파일러 팀이 어떤 이유로 구현하기 어렵거나 불가능하다고 생각합니다.

아니요, 실행하는 것은 전혀 어렵거나 불가능하지 않습니다. 여러분이 직접 실행했다는 사실은 그 사실에 대한 증거입니다.오히려, 이것은 믿을없을 정도로 나쁜 생각이기 때문에 우리는 당신이 이러한 실수를 저지르는 것을 방지하기 위해 그것을 허용하지 않습니다.

모니터로 호출합니다.ExitDisposable 내에서 Exit를 선택합니다.Dispose(폐기)는 다른 스레드가 잠금을 획득하려고 시도할 때 교착 상태를 발생시키는 경우가 대부분이며 무기한 차단되는 것으로 보입니다.저는 제가 하는 일의 신뢰성이 떨어지는 것과 wait statement가 lock statement에서 허용되지 않는 이유가 어떤 식으로든 관련이 있다고 생각합니다.

맞습니다, 당신은 우리가 그것을 불법으로 만든 이유를 발견했습니다.자물쇠 안에서 기다리는 것은 교착 상태를 만드는 방법입니다.

이유를 알 수 있을 것입니다. 임의 코드는 대기가 호출자에게 제어권을 반환하고 메서드가 다시 시작되는 시간 사이에 실행됩니다.이 임의 코드는 잠금 순서 변환을 생성하는 잠금을 해제하여 교착 상태에 빠질 수 있습니다.

더 나쁜 것은 코드가 다른 스레드에서 다시 시작될 수 있다는 것입니다(고급 시나리오에서는 일반적으로 대기를 수행한 스레드에서 다시 시작하지만 반드시 그렇지는 않습니다). 이 경우 잠금 해제는 잠금을 해제한 스레드와 다른 스레드에서 잠금을 해제합니다.그게 좋은 아이디어야?아니요.

나는 또한 그것을 하는 것이 "최악의 관행"이라는 것에 주목합니다.yield return富士山의 lock이지만, 저는 그것을 것입니다.그렇게 하는 것은 합법이지만, 우리가 그것을 불법으로 만들었으면 좋았을 것입니다.우리는 "기다림"에 대해 같은 실수를 저지르지 않을 것입니다.

메소드를 사용합니다.

 await mySemaphoreSlim.WaitAsync();
 try {
     await Stuff();
 } finally {
     mySemaphoreSlim.Release();
 }

이것은 사용자 1639030에 의한답변의 확장일 뿐입니다.


기본 버전


using System;
using System.Threading;
using System.Threading.Tasks;

public class SemaphoreLocker
{
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

    public async Task LockAsync(Func<Task> worker)
    {
        await _semaphore.WaitAsync();
        try
        {
            await worker();
        }
        finally
        {
            _semaphore.Release();
        }
    }

    // overloading variant for non-void methods with return type (generic T)
    public async Task<T> LockAsync<T>(Func<Task<T>> worker)
    {
        await _semaphore.WaitAsync();
        try
        {
            return await worker();
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

용도:

public class Test
{
    private static readonly SemaphoreLocker _locker = new SemaphoreLocker();

    public async Task DoTest()
    {
        await _locker.LockAsync(async () =>
        {
            // [async] calls can be used within this block 
            // to handle a resource by one thread. 
        });
        // OR
        var result = await _locker.LockAsync(async () =>
        {
            // [async] calls can be used within this block 
            // to handle a resource by one thread. 
        });
    }
}

확장 버전


의 한 LockAsync(제즈가 제안한 4번째 개정판부터) 완전히 교착 상태 안전하다고 주장하는 방법.

using System;
using System.Threading;
using System.Threading.Tasks;

public class SemaphoreLocker
{
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

    public async Task LockAsync(Func<Task> worker)
    {
        var isTaken = false;
        try
        {
            do
            {
                try
                {
                }
                finally
                {
                    isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
                }
            }
            while (!isTaken);
            await worker();
        }
        finally
        {
            if (isTaken)
            {
                _semaphore.Release();
            }
        }
    }

    // overloading variant for non-void methods with return type (generic T)
    public async Task<T> LockAsync<T>(Func<Task<T>> worker)
    {
        var isTaken = false;
        try
        {
            do
            {
                try
                {
                }
                finally
                {
                    isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
                }
            }
            while (!isTaken);
            return await worker();
        }
        finally
        {
            if (isTaken)
            {
                _semaphore.Release();
            }
        }
    }
}

용도:

public class Test
{
    private static readonly SemaphoreLocker _locker = new SemaphoreLocker();

    public async Task DoTest()
    {
        await _locker.LockAsync(async () =>
        {
            // [async] calls can be used within this block 
            // to handle a resource by one thread. 
        });
        // OR
        var result = await _locker.LockAsync(async () =>
        {
            // [async] calls can be used within this block 
            // to handle a resource by one thread. 
        });
    }
}

기본적으로 그것은 잘못된 일이 될 것입니다.

를 구현할 수 있는 두 가지 방법은 다음과 같습니다.

  • 자물쇠를 잡고 블록 끝에서만 잠금을 해제합니다.
    비동기 작업이 얼마나 오래 걸릴지 모르기 때문에 이것은 정말 나쁜 생각입니다.최소 시간 동안만 잠금을 유지해야 합니다.또한 스레드가 메서드가 아닌 잠금을 소유하기 때문에 작업 스케줄러에 따라 동일한 스레드에서 나머지 비동기 메서드를 실행하지 못할 수도 있습니다.

  • 하고 wait가 합니다.
    이 동일한 코드를 한 한 한 동기식 코드와 최소한의 됩니다.Monitor.Wait잠금 블록에서는 블록이 지속되는 동안 잠금을 소유해야 합니다.

기본적으로 여기에는 두 가지 경쟁 요구 사항이 있습니다. 첫 번째 요구 사항을 여기서 수행하려고 해서는 안 되며, 두 번째 접근 방식을 사용하려면 두 개의 분리된 잠금 블록을 대기 식으로 구분하여 코드를 훨씬 명확하게 만들 수 있습니다.

// Now it's clear where the locks will be acquired and released
lock (foo)
{
}
var result = await something;
lock (foo)
{
}

그래서 여러분이 자물쇠 블록 안에서 기다리는 것 자체를 금지함으로써, 언어는 여러분이 정말로 무엇을 하고 싶은지에 대해 생각하도록 강요하고, 여러분이 쓰는 코드에서 그 선택을 더 명확하게 합니다.

이것은 Building Async Coordination 프리미티브, Part 6: AsyncLock , http://winrtstoragehelper.codeplex.com/ , Windows 8 앱 스토어 및 .net 4.5에 관한 것입니다.

이것에 대한 나의 관점은 다음과 같습니다.

비동기/대기 언어 기능은 많은 것을 매우 쉽게 만들지만 비동기 호출을 사용하기 전에는 거의 접하지 못했던 시나리오인 재입장도 소개합니다.

대부분의 이벤트에서는 이벤트 핸들러에서 돌아온 후에 발생하는 일에 대한 단서가 없기 때문에 이벤트 핸들러의 경우 특히 그렇습니다.실제로 발생할 수 있는 한 가지는 첫 번째 이벤트 핸들러에서 대기 중인 비동기 메서드가 동일한 스레드에 있는 다른 이벤트 핸들러에서 호출된다는 것입니다.

Windows 8 앱 스토어 앱에서 발견한 실제 시나리오는 다음과 같습니다. 앱에는 두 개의 프레임이 있습니다. 파일/스토리지에 데이터를 로드/안전하게 로드하고 싶은 프레임에 들어오고 나가는 프레임입니다.OnNavigated To/From 이벤트는 저장 및 로드에 사용됩니다.저장 및 로드는 일부 비동기 유틸리티 기능(예: http://winrtstoragehelper.codeplex.com/) )에 의해 수행됩니다.프레임 1에서 프레임 2로 또는 다른 방향으로 이동할 때 비동기 로드 및 안전 작동이 호출되고 대기됩니다.이벤트 핸들러는 비동기 반환 void => 기다릴 수 없습니다.

그러나 유틸리티의 첫 번째 파일 열기 작업(예: 저장 함수 내부)도 비동기식이므로 첫 번째 대기는 프레임워크에 제어를 반환하고 나중에 두 번째 이벤트 핸들러를 통해 다른 유틸리티(로드)를 호출합니다.이제 로드가 동일한 파일을 열려고 하고 저장 작업을 위해 파일이 열려 있으면 ACCESS DENIED 예외와 함께 실패합니다.

저에게 최소한의 해결책은 사용 및 비동기 잠금을 통해 파일 액세스를 보호하는 것입니다.

private static readonly AsyncLock m_lock = new AsyncLock();
...

using (await m_lock.LockAsync())
{
    file = await folder.GetFileAsync(fileName);
    IRandomAccessStream readStream = await file.OpenAsync(FileAccessMode.Read);
    using (Stream inStream = Task.Run(() => readStream.AsStreamForRead()).Result)
    {
        return (T)serializer.Deserialize(inStream);
    }
}

그의 잠금은 기본적으로 하나의 잠금만으로 유틸리티에 대한 모든 파일 작업을 잠근다는 점에 유의하십시오. 이 잠금은 불필요하게 강력하지만 제 시나리오에서는 잘 작동합니다.

여기 제 테스트 프로젝트가 있습니다. http://winrtstoragehelper.codeplex.com/ 의 원본 버전과 Stephen Toub의 AsyncLock을 사용하는 수정된 버전을 테스트하는 Windows 8 앱 스토어 앱입니다.

다음 링크도 제안해도 될까요: http://www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx

Stephen Taub은 이 질문에 대한 솔루션을 구현했습니다. Building Async Coordination Premitives, Part 7: AsyncReaderWriterLock을 참조하십시오.

Stephen Taub은 업계에서 높은 평가를 받고 있기 때문에 그가 쓰는 모든 것은 견고할 것입니다.

그가 자신의 블로그에 올린 코드를 재현하지는 않겠지만 사용법을 알려드리겠습니다.

/// <summary>
///     Demo class for reader/writer lock that supports async/await.
///     For source, see Stephen Taub's brilliant article, "Building Async Coordination
///     Primitives, Part 7: AsyncReaderWriterLock".
/// </summary>
public class AsyncReaderWriterLockDemo
{
    private readonly IAsyncReaderWriterLock _lock = new AsyncReaderWriterLock(); 

    public async void DemoCode()
    {           
        using(var releaser = await _lock.ReaderLockAsync()) 
        { 
            // Insert reads here.
            // Multiple readers can access the lock simultaneously.
        }

        using (var releaser = await _lock.WriterLockAsync())
        {
            // Insert writes here.
            // If a writer is in progress, then readers are blocked.
        }
    }
}

에 구워지는 방법을 원한다면,NET 프레임워크, 사용SemaphoreSlim.WaitAsync대신.읽기/쓰기 잠금 기능은 제공되지 않지만 구현을 시도하고 테스트할 수 있습니다.

음, 보기 흉하게 생겼네요, 효과가 있는 것 같아요.

static class Async
{
    public static Task<IDisposable> Lock(object obj)
    {
        return TaskEx.Run(() =>
            {
                var resetEvent = ResetEventFor(obj);

                resetEvent.WaitOne();
                resetEvent.Reset();

                return new ExitDisposable(obj) as IDisposable;
            });
    }

    private static readonly IDictionary<object, WeakReference> ResetEventMap =
        new Dictionary<object, WeakReference>();

    private static ManualResetEvent ResetEventFor(object @lock)
    {
        if (!ResetEventMap.ContainsKey(@lock) ||
            !ResetEventMap[@lock].IsAlive)
        {
            ResetEventMap[@lock] =
                new WeakReference(new ManualResetEvent(true));
        }

        return ResetEventMap[@lock].Target as ManualResetEvent;
    }

    private static void CleanUp()
    {
        ResetEventMap.Where(kv => !kv.Value.IsAlive)
                     .ToList()
                     .ForEach(kv => ResetEventMap.Remove(kv));
    }

    private class ExitDisposable : IDisposable
    {
        private readonly object _lock;

        public ExitDisposable(object @lock)
        {
            _lock = @lock;
        }

        public void Dispose()
        {
            ResetEventFor(_lock).Set();
        }

        ~ExitDisposable()
        {
            CleanUp();
        }
    }
}

다음을 생성했습니다.MutexAsyncableStephen Toub의 AsyncLock 구현(이 블로그 게시물에서 토론)에서 영감을 얻은 클래스로, 의 드롭인 대체품으로 사용할 수 있습니다.locksync 또는 비동기 코드의 문:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace UtilsCommon.Lib;

/// <summary>
/// Class that provides (optionally async-safe) locking using an internal semaphore.
/// Use this in place of a lock() {...} construction.
/// Bear in mind that all code executed inside the worker must finish before the next
/// thread is able to start executing it, so long-running code should be avoided inside
/// the worker if at all possible.
///
/// Example usage for sync:
/// using (mutex.LockSync()) {
///     // ... code here which is synchronous and handles a shared resource ...
///     return[ result];
/// }
///
/// ... or for async:
/// using (await mutex.LockAsync()) {
///     // ... code here which can use await calls and handle a shared resource ...
///     return[ result];
/// }
/// </summary>
public sealed class MutexAsyncable {
    #region Internal classes

    private sealed class Releaser : IDisposable {
        private readonly MutexAsyncable _toRelease;
        internal Releaser(MutexAsyncable toRelease) { _toRelease = toRelease; }
        public void Dispose() { _toRelease._semaphore.Release(); }
    }

    #endregion

    private readonly SemaphoreSlim _semaphore = new(1, 1);
    private readonly Task<IDisposable> _releaser;

    public MutexAsyncable() {
        _releaser = Task.FromResult((IDisposable)new Releaser(this));
    }

    public IDisposable LockSync() {
        _semaphore.Wait();
        return _releaser.Result;
    }

    public Task<IDisposable> LockAsync() {
        var wait = _semaphore.WaitAsync();
        if (wait.IsCompleted) { return _releaser; }
        else {
            // Return Task<IDisposable> which completes once WaitAsync does
            return wait.ContinueWith(
                (_, state) => (IDisposable)state!,
                _releaser.Result,
                CancellationToken.None,
                TaskContinuationOptions.ExecuteSynchronously,
                TaskScheduler.Default
            );
        }
    }
}

만약 당신이 사용한다면 위의 것들을 사용하는 것이 안전합니다.NET 5+는 절대 던지지 않기 때문입니다.ThreadAbortException.

확장도 만들었습니다.SemaphoreLocker대답에서 영감을 받은 수업, 이것은 범용 대체가 될 수 있습니다.lock동기식 또는 비동기식으로 사용할 수 있습니다.위의 것보다 효율성이 떨어집니다.MutexAsyncable더 자원을 코드가 을 해제하도록 작자코완기잠해강techn이금있록할더제제다많당를합니리로스소으은본적만지하는도점이면하을업되료가드기▁and,techn▁resources▁to▁allocates, ).IDisposable에 의해 됩니다.MutexAsyncable코드를 호출하여 처리할 수 없으므로 교착 상태가 발생합니다.또한 다음과 같은 가능성을 처리하기 위한 추가 시도/최종 코드가 있습니다.ThreadAbortException그래서 더 일찍 사용할 수 있어야 합니다.NET 전버

using System;
using System.Threading;
using System.Threading.Tasks;

namespace UtilsCommon.Lib;

/// <summary>
/// Class that provides (optionally async-safe) locking using an internal semaphore.
/// Use this in place of a lock() {...} construction.
/// Bear in mind that all code executed inside the worker must finish before the next thread is able to
/// start executing it, so long-running code should be avoided inside the worker if at all possible.
///
/// Example usage:
/// [var result = ]await _locker.LockAsync(async () => {
///     // ... code here which can use await calls and handle a shared resource one-thread-at-a-time ...
///     return[ result];
/// });
///
/// ... or for sync:
/// [var result = ]_locker.LockSync(() => {
///     // ... code here which is synchronous and handles a shared resource one-thread-at-a-time ...
///     return[ result];
/// });
/// </summary>
public sealed class SemaphoreLocker : IDisposable {
    private readonly SemaphoreSlim _semaphore = new(1, 1);

    /// <summary>
    /// Runs the worker lambda in a locked context.
    /// </summary>
    /// <typeparam name="T">The type of the worker lambda's return value.</typeparam>
    /// <param name="worker">The worker lambda to be executed.</param>
    public T LockSync<T>(Func<T> worker) {
        var isTaken = false;
        try {
            do {
                try {
                }
                finally {
                    isTaken = _semaphore.Wait(TimeSpan.FromSeconds(1));
                }
            }
            while (!isTaken);
            return worker();
        }
        finally {
            if (isTaken) {
                _semaphore.Release();
            }
        }
    }

    /// <inheritdoc cref="LockSync{T}(Func{T})" />
    public void LockSync(Action worker) {
        var isTaken = false;
        try {
            do {
                try {
                }
                finally {
                    isTaken = _semaphore.Wait(TimeSpan.FromSeconds(1));
                }
            }
            while (!isTaken);
            worker();
        }
        finally {
            if (isTaken) {
                _semaphore.Release();
            }
        }
    }

    /// <summary>
    /// Runs the worker lambda in an async-safe locked context.
    /// </summary>
    /// <typeparam name="T">The type of the worker lambda's return value.</typeparam>
    /// <param name="worker">The worker lambda to be executed.</param>
    public async Task<T> LockAsync<T>(Func<Task<T>> worker) {
        var isTaken = false;
        try {
            do {
                try {
                }
                finally {
                    isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
                }
            }
            while (!isTaken);
            return await worker();
        }
        finally {
            if (isTaken) {
                _semaphore.Release();
            }
        }
    }

    /// <inheritdoc cref="LockAsync{T}(Func{Task{T}})" />
    public async Task LockAsync(Func<Task> worker) {
        var isTaken = false;
        try {
            do {
                try {
                }
                finally {
                    isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
                }
            }
            while (!isTaken);
            await worker();
        }
        finally {
            if (isTaken) {
                _semaphore.Release();
            }
        }
    }

    /// <summary>
    /// Releases all resources used by the current instance of the SemaphoreLocker class.
    /// </summary>
    public void Dispose() {
        _semaphore.Dispose();
    }
}

는 사해보니다습았용▁a다▁using▁did를 사용해 보았습니다.Monitor(아래 코드) 작동하는 것처럼 보이지만 GOTCHA가 있습니다... 스레드가 여러 개 있을 때 제공됩니다...

System.Threading.SynchronizationLockException동기화되지 않은 코드 블록에서 개체 동기화 메서드를 호출했습니다.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace MyNamespace
{
    public class ThreadsafeFooModifier : 
    {
        private readonly object _lockObject;

        public async Task<FooResponse> ModifyFooAsync()
        {
            FooResponse result;
            Monitor.Enter(_lockObject);
            try
            {
                result = await SomeFunctionToModifyFooAsync();
            }
            finally
            {
                Monitor.Exit(_lockObject);
            }
            return result;
        }
    }
}

이전에는 단순하게 이 작업을 수행했지만 ASP에서 수행했습니다.NET 컨트롤러로 인해 교착 상태가 발생했습니다.

public async Task<FooResponse> ModifyFooAsync()
{
    lock(lockObject)
    {
        return SomeFunctionToModifyFooAsync.Result;
    }
}

언급URL : https://stackoverflow.com/questions/7612602/why-cant-i-use-the-await-operator-within-the-body-of-a-lock-statement

반응형