Ключевое слово await в C # (.NET Async CTP) не допускается из оператора блокировки.
Из MSDN :
Выражение 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();
}
Однако это не работает, как ожидалось. Вызов Monitor.Exit в ExitDisposable.Dispose, кажется, блокируется на неопределенное время (большую часть времени), вызывая взаимоблокировки, когда другие потоки пытаются получить блокировку. Я подозреваю, что ненадежность моей работы и заявления о причине ожидания не допускаются в операторе блокировки, как-то связаны.
Кто-нибудь знает, почему ожидание не допускается в теле оператора блокировки?
источник
Ответы:
Нет, это совсем не сложно или невозможно реализовать - тот факт, что вы реализовали это самостоятельно, свидетельствует об этом. Скорее, это невероятно плохая идея, и поэтому мы не допускаем ее, чтобы защитить вас от этой ошибки.
Правильно, вы обнаружили, почему мы сделали это незаконным. Ожидание внутри замка - это рецепт создания тупиков.
Я уверен, что вы понимаете, почему: произвольный код запускается между временем, когда ожидание возвращает управление вызывающей стороне, и возобновлением метода . Этот произвольный код может снимать блокировки, которые производят инверсии порядка блокировки, и, следовательно, взаимоблокировки.
Хуже того, код может возобновиться в другом потоке (в расширенных сценариях; обычно вы снова берете трубку в потоке, который ожидал, но не обязательно), и в этом случае разблокировка будет разблокировать блокировку в потоке, отличном от потока, который занял из замка. Это хорошая идея? Нет .
Я отмечаю, что это также «худшая практика» делать
yield return
внутриlock
, по той же причине. Это законно, но хотелось бы, чтобы мы сделали это незаконно. Мы не собираемся делать ту же ошибку за «жду».источник
SemaphoreSlim.WaitAsync
класс был добавлен в .NET Framework задолго до того, как вы опубликовали этот ответ, я думаю, мы можем смело предположить, что это возможно сейчас. Независимо от этого, ваши комментарии о сложности реализации такой конструкции все еще остаются в силе.Используйте
SemaphoreSlim.WaitAsync
метод.источник
Stuff
я не вижу в этом никакого пути ...mySemaphoreSlim = new SemaphoreSlim(1, 1)
для того, чтобы работать какlock(...)
?По сути, это было бы неправильно.
Есть два способа это может быть реализовано:
Держите замок, открывая его только в конце блока .
Это действительно плохая идея, так как вы не знаете, сколько времени займет асинхронная операция. Вы должны держать замки только в течение минимального количества времени. Это также потенциально невозможно, так как поток владеет блокировкой, а не методом - и вы можете даже не выполнять остальную часть асинхронного метода в том же потоке (в зависимости от планировщика задач).
Освободите блокировку в await и восстановите ее, когда вернется await.
Это нарушает принцип IMO наименьшего удивления, когда асинхронный метод должен вести себя как можно ближе к аналогичному синхронному коду - если вы не используете
Monitor.Wait
в блокировочном блоке, вы ожидаете владеть замком на весь срок действия блока.Таким образом, в основном здесь есть два конкурирующих требования - вы не должны пытаться выполнить первое здесь, и если вы хотите использовать второй подход, вы можете сделать код намного более понятным, имея два отдельных блока блокировки, разделенных выражением await:
Таким образом, запретив вам ожидать в самом блоке блокировки, язык заставляет вас думать о том, что вы действительно хотите сделать, и делать этот выбор более ясным в коде, который вы пишете.
источник
SemaphoreSlim.WaitAsync
класс был добавлен в .NET Framework задолго до того, как вы опубликовали этот ответ, я думаю, мы можем смело предположить, что это возможно сейчас. Независимо от этого, ваши комментарии о сложности реализации такой конструкции все еще остаются в силе.Это просто продолжение этого ответа .
Использование:
источник
try
блока - если между ними происходит исключение,WaitAsync
иtry
семафор никогда не будет освобожден (тупик). С другой стороны, перемещениеWaitAsync
вызова вtry
блок создаст другую проблему, когда семафор может быть освобожден без получения блокировки. См. Связанный поток, где эта проблема была объяснена: stackoverflow.com/a/61806749/7889645Это относится к http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx , http://winrtstoragehelper.codeplex.com/ , магазину приложений Windows 8 и .net 4.5
Вот мой взгляд на это:
Функция языка async / await делает многие вещи довольно простыми, но она также представляет сценарий, с которым редко сталкивались до того, как стало так легко использовать асинхронные вызовы: reentrance.
Это особенно верно для обработчиков событий, потому что для многих событий вы не имеете никакого представления о том, что происходит после того, как вы вернетесь из обработчика событий. В действительности может произойти то, что асинхронный метод, который вы ожидаете в первом обработчике событий, вызывается из другого обработчика событий, все еще находящегося в том же потоке.
Вот реальный сценарий, с которым я столкнулся в приложении Windows 8 App Store: у моего приложения есть два фрейма: вход и выход из фрейма, я хочу загрузить / сохранить некоторые данные в файл / хранилище. События OnNavigatedTo / From используются для сохранения и загрузки. Сохранение и загрузка выполняются с помощью некоторой функции асинхронной утилиты (например, http://winrtstoragehelper.codeplex.com/ ). При переходе от кадра 1 к кадру 2 или в другом направлении вызывается и ожидается асинхронная загрузка и безопасные операции. Обработчики событий становятся асинхронными, возвращая void => их нельзя ожидать.
Однако первая операция открытия файла (позволяет говорить: внутри функции сохранения) утилиты также асинхронна, и поэтому первое ожидание возвращает управление платформе, которая через некоторое время вызывает другую утилиту (загрузку) через второй обработчик событий. Теперь загрузка пытается открыть тот же файл, и если файл уже открыт для операции сохранения, происходит сбой с исключением ACCESSDENIED.
Минимальное решение для меня - обеспечить доступ к файлу через использование и AsyncLock.
Обратите внимание, что его блокировка в основном блокирует все файловые операции для утилиты всего одной блокировкой, которая неоправданно сильна, но прекрасно работает для моего сценария.
Вот мой тестовый проект: приложение магазина приложений для Windows 8 с некоторыми тестовыми вызовами для исходной версии http://winrtstoragehelper.codeplex.com/ и моей модифицированной версии, в которой используется AsyncLock от Стивена Тауба http: //blogs.msdn. com / b / pfxteam / archive / 2012/02/12 / 10266988.aspx .
Могу ли я также предложить эту ссылку: http://www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx
источник
Стивен Тауб реализовал решение этого вопроса, см. Построение асинхронных координационных примитивов, часть 7: AsyncReaderWriterLock .
Стивен Тауб высоко ценится в отрасли, поэтому все, что он пишет, вероятно, будет солидным.
Я не буду воспроизводить код, который он разместил в своем блоге, но я покажу вам, как его использовать:
Если вам нужен метод, встроенный в .NET Framework, используйте
SemaphoreSlim.WaitAsync
вместо этого. Вы не получите блокировку чтения / записи, но вы получите испытанную и протестированную реализацию.источник
SemaphoreSlim.WaitAsync
и в .NET Framework. Все, что делает этот код, это добавляет концепцию блокировки чтения / записи.Хм, выглядит некрасиво, кажется, работает.
источник
Я попытался использовать монитор (код ниже), который, кажется, работает, но имеет GOTCHA ... когда у вас есть несколько потоков, он даст ... System.Threading.SynchronizationLockException Метод синхронизации объекта был вызван из несинхронизированного блока кода.
До этого я просто делал это, но это было в контроллере ASP.NET, поэтому это привело к тупику.
public async Task<FooResponse> ModifyFooAsync() { lock(lockObject) { return SomeFunctionToModifyFooAsync.Result; } }
источник