.Net словарь не получает значение непосредственно после вставки в редких случаях

Я написал следующий фрагмент кода:

public interface IModelToViewModelServiceBase<in TDomain, out TViewModel>
    where TDomain : class, IDataModel
    where TViewModel : class, IDataModelViewModel
{
    TViewModel GetViewModel(TDomain model);
}

public abstract class ModelToViewModelServiceBase<TDomain, TViewModel> : IModelToViewModelServiceBase<TDomain, TViewModel>
    where TDomain : class, IDataModel
    where TViewModel : class, IDataModelViewModel
{
    private readonly IDictionary<TDomain, TViewModel> _modelToViewModel
        = new Dictionary<TDomain, TViewModel>();

    protected abstract TViewModel Create(TDomain model);

    public TViewModel GetViewModel(TDomain model)
    {
        if (model == null) return null;
        if (!_modelToViewModel.ContainsKey(model))
            _modelToViewModel[model] = Create(model);

        return _modelToViewModel[model];
    }
}

Цель этого класса не имеет значения для проблемы. В редких случаях я получаю KeyNotFound на обратной линии. Однако, насколько я понимаю, предыдущие условия if должны предотвратить это. Никакой ключ не может быть нулевым, и полученное значение, если оно еще не существовало, было добавлено в предыдущей инструкции.

Что мне здесь не хватает?

Я сейчас разработал обходной путь:

public interface IModelToViewModelServiceBase<in TDomain, out TViewModel>
    where TDomain : class, IDataModel
    where TViewModel : class, IDataModelViewModel
{
    TViewModel GetViewModel(TDomain model);
}

public abstract class ModelToViewModelServiceBase<TDomain, TViewModel> : IModelToViewModelServiceBase<TDomain, TViewModel>
    where TDomain : class, IDataModel
    where TViewModel : class, IDataModelViewModel
{
    private readonly IDictionary<TDomain, TViewModel> _modelToViewModel
        = new Dictionary<TDomain, TViewModel>();

    protected abstract TViewModel Create(TDomain model);

    public TViewModel GetViewModel(TDomain model)
    {
        if (model == null) return null;
        TViewModel viewModel = null;
        if (!_modelToViewModel.ContainsKey(model))
        {
            viewModel = Create(model);
            _modelToViewModel[model] = viewModel;
        }
        else
            viewModel = _modelToViewModel[model];

        return viewModel;
    }
}

Это похоже на работу. Однако этот обходной путь не должен быть необходимым. Может быть, этот обходной путь еще лучше, потому что теперь выполняется на один меньше доступ к словарю. Тем не менее, предыдущая версия должна была работать всегда.

Обновить после ответа:

@evk и @mjwills оба правы. Я не считал, что мой код небезопасен для одновременного использования, и несколько потоков обращаются к нему. Следовательно, после предложений @mjwills код выглядит следующим образом:

public interface IModelToViewModelServiceBase<in TDomain, out TViewModel>
    where TDomain : class, IDataModel
    where TViewModel : class, IDataModelViewModel
{
    TViewModel GetViewModel(TDomain model);
}

public abstract class ModelToViewModelServiceBase<TDomain, TViewModel> : IModelToViewModelServiceBase<TDomain, TViewModel>
    where TDomain : class, IDataModel
    where TViewModel : class, IDataModelViewModel
{
    private readonly ConcurrentDictionary<TDomain, TViewModel> _modelToViewModel
        = new ConcurrentDictionary<TDomain, TViewModel>();

    protected abstract TViewModel Create(TDomain model);

    public TViewModel GetViewModel(TDomain model)
    {
        if (model == null) return null;

        return _modelToViewModel.GetOrAdd(model, Create); ;
    }
}

1 ответ

Решение

Вы упомянули, что ваш код может быть доступен из нескольких потоков, но ваш код не является потокобезопасным. Это не безопасно писать и читать на регулярной Dictionary из нескольких потоков (если вы только читаете и никогда не пишете - тогда все в порядке). Вы можете подумать, что если вы никогда не удалите элементы из словаря, то KeyNotFoundException не может быть брошено в вашем случае, но это не так. Когда вы используете структуру таким образом, чтобы она не была предназначена - может случиться что угодно. Например, рассмотрим этот код:

class Program
{
    public static void Main(string[] args)
    {
        var service = new ModelToViewModelServiceBase();
        new Thread(() => AddServices(service)).Start();
        new Thread(() => AddServices(service)).Start();
        new Thread(() => AddServices(service)).Start();
        new Thread(() => AddServices(service)).Start();
        Console.ReadKey();
    }

    private static void AddServices(ModelToViewModelServiceBase services) {
        for (int i = 0; i < 100000; i++) {
            services.GetViewModel(i);
        }
    }
}

public class ModelToViewModelServiceBase {
    private readonly IDictionary<int, string> _modelToViewModel
        = new Dictionary<int, string>();

    protected string Create(int model) {
        return model.ToString();
    }

    public string GetViewModel(int model) {
        if (!_modelToViewModel.ContainsKey(model))
            _modelToViewModel[model] = Create(model);

        return _modelToViewModel[model];
    }
}

Когда вы запустите его - вы почти всегда получите KeyNotFoundExceptionв то время как вы никогда не удаляете элементы из словаря. Это из-за того, как Dictionary реализовано внутри, и я думаю, что точные детали не имеют отношения к этому вопросу.

Короче говоря - просто не используйте не-поточно-ориентированную структуру из нескольких потоков (кроме случаев, когда все потоки только читают и никогда не пишут) без надлежащей синхронизации, даже если вам кажется, что она будет работать нормально.

Другие вопросы по тегам