Должны ли асинхронные значения быть потокобезопасными?

#c# #.net-core

#c# #.net-core

Вопрос:

В недавнем обзоре PR возник вопрос о том, AsyncLocal<IDictionary<>> следует ли использовать ConcurrentDictionary<> . Я думаю, что в этом нет необходимости, потому что к асинхронному значению не будут обращаться несколько потоков одновременно. Но я хочу быть уверен.

Нужно ли нам беспокоиться о потокобезопасности объектов, хранящихся в AsyncLocal? Пожалуйста, объясните, почему или почему нет. Бонусные баллы за модульный тест, демонстрирующий утвержденный ответ.

Комментарии:

1. Может ли происходить одновременный доступ зависит от контекста синхронизации .

2. AsyncLocal позволяет нескольким задачам или потокам иметь разные ссылки на словарь, используя только одну переменную. Это не гарантирует, что они на самом деле различны. Итак, вам нужно беспокоиться, просмотрите назначения.

Ответ №1:

AsyncLocal доступно для всех потоков в асинхронном потоке управления.

Давайте рассмотрим этот пример:

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

namespace ConsoleApp15
{
    class Program
    {

        static AsyncLocal<string> _asyncLocalString = new AsyncLocal<string>();

        static async Task Main(string[] args)
        {
            _asyncLocalString.Value = "TestString";

            var tasks = Enumerable.Range(0, 10).Select(_ => GetString());
            var results = await Task.WhenAll(tasks);
        }

        private static async Task<string> GetString()
        {
            await Task.Yield();
            return _asyncLocalString.Value   Thread.CurrentThread.ManagedThreadId;
        }
    }
}

  

Здесь несколько потоков могут получить доступ к значению «testString» одновременно.

введите описание изображения здесь

Кроме того, после извлечения значения из Async local его необходимо обрабатывать так же, как и любую другую ссылку. Если он используется в обратных вызовах, возвращается вызывающей стороне, фиксируется при закрытии и т.д., Он может быть предоставлен другим потокам. При ссылочных типах значения могут быть изменены извне и могут возникнуть условия гонки.

Обновление: Вот пример со словарем:

 using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp15
{
    class Program
    {

        static AsyncLocal<Dictionary<string, int>> _asyncLocalDict = new AsyncLocal<Dictionary<string, int>>();

        static async Task Main(string[] args)
        {
            _asyncLocalDict.Value = new Dictionary<string, int>();

            var tasks = Enumerable.Range(0, 10).Select(_ => Race());
            await Task.WhenAll(tasks);
        }

        private static async Task Race()
        {
            await Task.Yield();
            var dict = _asyncLocalDict.Value;
            if (!dict.ContainsKey("race")) dict["race"] = 0;
            dict["race"]  ;
        }
    }
}
  

введите описание изображения здесь

Комментарии:

1. Я вроде бы верю вам, но пример, похоже, на самом деле не доказывает это, потому что каждая задача может иметь эквивалентные, но разные экземпляры строки. Может быть, лучше использовать что-то, что действительно может быть изменено.

2. @E-Riz Я добавил тот же пример со словарем