.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
реализовано внутри, и я думаю, что точные детали не имеют отношения к этому вопросу.
Короче говоря - просто не используйте не-поточно-ориентированную структуру из нескольких потоков (кроме случаев, когда все потоки только читают и никогда не пишут) без надлежащей синхронизации, даже если вам кажется, что она будет работать нормально.