Короткий ответ
Вы всегда должны быть осторожны при использовании асинхронного.
Более длинный ответ
Это зависит от ваших SynchronizationContext и TaskScheduler, и что вы подразумеваете под «безопасным».
Когда ваш код awaits
что-то, он создает продолжение и оборачивает его в задачу, которая затем публикуется в TaskScheduler текущего SynchronizationContext.Затем контекст определит, когда и где будет выполняться продолжение.Планировщик по умолчанию просто использует пул потоков, но различные типы приложений могут расширять планировщик и предоставлять более сложную логику синхронизации.
Если вы пишете приложение, которое не имеет SynchronizationContext (например, консольное приложение или что-нибудь в ядре .NET ), продолжение просто помещается в пул потоков и может выполняться параллельно с вашим основным потоком.В этом случае вы должны использовать lock
или синхронизированные объекты, такие как ConcurrentDictionary<>
вместо Dictionary<>
, для всего, кроме локальных ссылок или ссылок, которые закрыты с задачей.
Если вы пишете приложение WinForms, продолжения помещаются в очередь сообщений и будут выполняться в основном потоке.Это делает безопасным использование несинхронизированных объектов.Однако есть и другие проблемы, такие как взаимоблокировки .И, конечно, если вы порождаете какие-либо потоки, вы должны убедиться, что они используют lock
или объекты Concurrent, и любые вызовы пользовательского интерфейса должны быть перенаправлены обратно в поток пользовательского интерфейса .Кроме того, если вы достаточно сумасшедший, чтобы написать приложение WinForms с более чем одним обработчиком сообщений (это весьма необычно), вам придется беспокоиться о синхронизации любых распространенных переменных.
Если вы пишете ASP.NETВ приложении SynchronizationContext будет гарантировать, что для данного запроса не будут выполняться одновременно два потока.Ваше продолжение может выполняться в другом потоке (из-за функции производительности, известной как гибкость потока ), но они всегда будут иметь одинаковый SynchronizationContext, и вам гарантировано, что никакие два потока не получат доступ к вашим переменным одновременно(конечно, при условии, что они не являются статичными, в этом случае они охватывают HTTP-запросы и должны быть синхронизированы).Кроме того, конвейер будет блокировать параллельные запросы для одного и того же сеанса, чтобы они выполнялись последовательно, поэтому ваше состояние сеанса также защищено от проблем с потоками.Однако вам все равно нужно беспокоиться о взаимоблокировках.
И, конечно, вы можете написать свой собственный SynchronizationContext и назначить его своим потокам, что означает, что вы определяете свои собственные правила синхронизации, которые будут использоваться сasync
.
См. Также Как выполнить и дождаться выполнения потока управления в .NET?