Почему я не могу использовать оператор 'await' в теле оператора блокировки?
ключевое слово await в C# (.NET Async CTP) не допускается из инструкции lock.
С MSDN:
An
выражение await не может быть использована в синхронной функции, в запросе
выражение в блоке catch или finally обработки исключений
заявление,в блоке оператора блокировки, или в небезопасном контексте.
Я предполагаю, что это либо сложно, либо невозможно команда компилятора для реализации по какой-то причине.
Я попытался обойти с помощью оператора using:
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();
}
однако это не работает, как ожидалось. Вызов для мониторинга.Выход в пределах ExitDisposable.Dispose, похоже, блокируется на неопределенный срок (большую часть времени), вызывая взаимоблокировки, поскольку другие потоки пытаются получить блокировку. Я подозреваю, что ненадежность моей работы вокруг и причина ожидания заявления не допускаются в заявлении блокировки каким-то образом связанный.
кто-нибудь знает почему ожидание не допускается в теле оператора блокировки?
8 ответов:
Я предполагаю, что это либо трудно, либо невозможно для команды компилятора реализовать по какой-то причине.
нет, это не так уж сложно или невозможно реализовать-тот факт, что вы реализовали себя является свидетельством этого факта. Скорее,это невероятно плохая идея и поэтому мы не позволяем этого, чтобы защитить вас от этой ошибки.
вызов для мониторинга.Выход в пределах ExitDisposable.Распоряжаться кажется блок бесконечно (большую часть времени) вызывая взаимоблокировки, поскольку другие потоки пытаются получить блокировку. Я подозреваю, что ненадежность моей работы и причина, по которой операторы ожидания не разрешены в операторе блокировки, каким-то образом связаны.
правильно, вы обнаружили, почему мы сделали это незаконно. ожидание внутри замка-это рецепт для создания тупиков.
Я уверен, вы можете понять, почему: произвольный код выполняется между временем ожидания возвращает управление вызывающему объекту и метод возобновляется. Этот произвольный код может вынимать блокировки, которые производят инверсии порядка блокировки, и, следовательно, тупики.
хуже, код может возобновиться в другом потоке (в расширенных сценариях; обычно вы снова поднимаете поток, который ожидал, но не обязательно), и в этом случае разблокировка будет разблокировать блокировку в другом потоке, чем поток, который вынул блокировку. Это хорошая идея? Нет.
Я отмечаю, что это также "Худшая практика", чтобы сделать
yield returnвнутриlock, по той же причине. Это законно, но я хотел бы, чтобы мы сделали это незаконным. Мы не собираемся делать ту же ошибку на "ждут".
использовать
SemaphoreSlim.WaitAsyncметод.await mySemaphoreSlim.WaitAsync(); try { await Stuff(); } finally { mySemaphoreSlim.Release(); }
в принципе, это было бы неправильно.
есть два способа это может быть реализованы:
держите замок, только отпуская его в конце блока.
Это действительно плохая идея, так как вы не знаете, сколько времени займет асинхронная операция. Вы должны держать замки только для минимальный количество времени. Это также потенциально невозможно, как thread владеет блокировка, а не метод - и вы даже не можете выполнить остальную часть асинхронного метода в том же потоке (в зависимости от планировщика задач).отпустите блокировку в await, и повторно получить его, когда await возвращает
Это нарушает принцип наименьшего удивления IMO, где асинхронный метод должен вести себя как можно ближе, как эквивалентный синхронный код-если вы не используетеMonitor.Waitв блоке блокировки, вы ожидаете владеть блокировка на время действия блока.Так что в основном есть два конкурирующих требования здесь - вы не должны быть попытка чтобы сделать первый здесь, и если вы хотите принять второй подход, вы можете сделать код намного яснее, имея два разделенных блока блокировки, разделенных выражением ожидания:
// Now it's clear where the locks will be acquired and released lock (foo) { } var result = await something; lock (foo) { }таким образом, запрещая вам ждать в самом блоке блокировки, язык заставляет вас думать о том, что вы действительно хотите сделать, и сделать этот выбор более ясным в коде, который вы пишете.
это относится к http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx , http://winrtstoragehelper.codeplex.com/, Windows 8 app store и .net 4.5
вот мой угол зрения на это:
функция языка async / await упрощает многие вещи, но также вводит сценарий, который был редко сталкиваюсь, прежде чем это было так просто использовать асинхронные вызовы: reentrance.
Это особенно верно для случая обработчики, потому что для многих событий вы не имеете никакого понятия о том, что происходит после возвращения из обработчика событий. Одна вещь, которая может произойти на самом деле, заключается в том, что асинхронный метод, который вы ожидаете в первом обработчике событий, вызывается из другого обработчика событий, все еще находящегося на та же тема.
вот реальный сценарий, с которым я столкнулся в приложении Windows 8 App store: Мое приложение имеет два кадра: входя и выходя из кадра, я хочу загрузить/сохранить некоторые данные в файл/хранилище. События OnNavigatedTo/From используются для сохранения и загрузки. Сохранение и загрузка выполняется с помощью некоторой асинхронной служебной функции (например,http://winrtstoragehelper.codeplex.com/). При переходе от кадра 1 к кадру 2 или в другом направлении асинхронная загрузка и безопасные операции вызываются и ожидаются. Обработчики событий становятся асинхронными, возвращая void => их нельзя ждать.
однако первая операция открытия файла (позволяет сказать: внутри функции сохранения) утилиты асинхронный тоже и поэтому первый await возвращает управление в фреймворк, который через некоторое время вызывает другую утилиту (load) через второй обработчик событий. Нагрузка сейчас пытается открыть тот же файл и если файл уже открыт для операции сохранения, завершается с ошибкой ACCESSDENIED исключение.
минимальное решение для меня-обеспечить доступ к файлу через using и AsyncLock.
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); } }обратите внимание, что его замок по сути блокирует все операции с файлами утилиты только с одним замком, который излишне силен, но отлично работает для моего сценария.
здесь Это мой тестовый проект: приложение Windows 8 app store с некоторыми тестовыми вызовами для исходной версии от http://winrtstoragehelper.codeplex.com/ и моя модифицированная версия, которая использует AsyncLock от Stephen Toub http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx.
могу ли я также предложить это ссылка на сайт: http://www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx
Это просто расширение до ответ.
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(); } } }использование:
public class Test { private static readonly SemaphoreLocker _locker = new SemaphoreLocker(); public async Task DoTest() { await _locker.LockAsync(async () => { // [asyn] calls can be used within this block // to handle a resource by one thread. }); } }
Хм, выглядит некрасиво, кажется, работает.
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(); } } }
Стивен Тауб реализовал решение этого вопроса, см. Построение Асинхронных Координационных Примитивов, Часть 7: AsyncReaderWriterLock.
Стивен Тауб высоко ценится в отрасли, поэтому все, что он пишет, скорее всего, будет прочным.
Я не буду воспроизводить код, который он опубликовал в своем блоге, но я покажу, как его использовать:
/// <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 framework, используйте
SemaphoreSlim.WaitAsyncвместо. Вы не получите блокировка чтения / записи, но вы получите проверенную реализацию.
Я попытался использовать монитор (код ниже), который, кажется, работает, но имеет GOTCHA... когда у вас есть несколько потоков, он даст... Система.Нарезка резьбы.Метод синхронизации объекта 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; } }
Comments