2015-08-02 4 views
4

В нашем текущем проекте мы регистрируем сопоставление в статических конструкторах классов, которые вызывается несколькими потоками. Сопоставления в статических конструкторах применимы только для этих классов. Но все же несколько вызовов CreateMap могут быть запущены в одно и то же время. Более того, иногда (в основном как копии/прошлые выпуски) одинаковые сопоставления могут быть зарегистрированы в статических конструкторах разных классов.Является ли Automapper Mapper.CreateMap потокобезопасным?

Я попытался установить, является ли Mapper.CreateMap потокобезопасным или нет. И я нашел только следующее:

В сообщении Is Mapper.Map in AutoMapper thread-safe с 2012 года есть замечание в ответе nemesv о том, что CreateMap не является потокобезопасным, и его никогда не будет.

Но я нашел ошибку на GitHub Static DynamicMap and CreateMap APIs should be thread-safe с 2014 года, отмеченную как закрытую в 3.2 релизе. Это говорит о том, что CreateMap теперь должен быть потокобезопасным.

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

EDIT После некоторого дополнительного тестирования, кажется, что поведение CreateMap очень интересно:

Я использовал следующий код для тестирования

public void Test() 
    { 
     var items = new List<EntityA>(); 
     for (int i = 0; i < 100000; i++) 
     { 
      items.Add(new EntityA { FirstName = "A" + i }); 
     } 

     ManualResetEvent stopChangingMappingFunction = new ManualResetEvent(false); 

     Thread t1 = new Thread(() => 
     { 

      int i = 1; 
      while (true) 
      { 
       if (stopChangingMappingFunction.WaitOne(TimeSpan.Zero)) 
        return; 

       var i1 = i++; 
       Mapper.CreateMap<EntityA, EntityB>().ForMember(x => x.Age, y => y.ResolveUsing(new Func<EntityA, object>(a => i1))); 
      } 
     }); 

     Thread t2 = new Thread(() => 
     { 
      int i = -1; 
      while (true) 
      { 
       if (stopChangingMappingFunction.WaitOne(TimeSpan.Zero)) 
        return; 

       var i1 = i--; 
       Mapper.CreateMap<EntityA, EntityB>().ForMember(x => x.Age, y => y.ResolveUsing(new Func<EntityA, object>(a => i1))); 
      } 
     }); 

     List<int> distinctAges1 = null; 
     List<int> distinctAges2 = null; 

     Thread t3 = new Thread(() => 
     { 
      Thread.Sleep(1000); 

      var res = Mapper.Map<IList<EntityA>, IList<EntityB>>(items); 
      distinctAges1 = res.Select(x => x.Age).Distinct().ToList(); 

      Thread.Sleep(1000); 

      var res2 = Mapper.Map<IList<EntityA>, IList<EntityB>>(items); 
      distinctAges2 = res.Select(x => x.Age).Distinct().ToList(); 

      stopChangingMappingFunction.Set(); 
     }); 

     t1.Start(); 
     t2.Start(); 
     t3.Start(); 

     t1.Join(); 
     t2.Join(); 
     t3.Join(); 

     Console.WriteLine("First Mapping: " + string.Join(", ", distinctAges1.ToArray())); 
     Console.WriteLine("Second Mapping: " + string.Join(", ", distinctAges2.ToArray())); 
     Console.ReadKey(); 
    } 

public class EntityA 
{ 
    public string FirstName { get; set; } 
} 

public class EntityB 
{ 
    public string FirstName { get; set; } 
    public int Age { get; set; } 
} 

Во всех моих тестах, когда метод первой карты называется это означает что CreateMap заморожен, и больше никаких изменений в функции сопоставления не может быть сделано (distinctAges1 всегда было единственным уникальным значением, а одно значение - в distinctAges2). Изменение функции отображения из двух потоков иногда приводит к увеличению чередующихся значений Возраста от отрицательного к положительному числу (тесты заканчиваются высоким значением разного возраста). Но иногда поведение было совершенно иным, и итерация возраста прекращалась при значении 1 или -1. Кажется, есть некоторые внутренние механизмы замораживания изменений в функции отображения, если эта функция сопоставления изменяется из большего количества потоков. Но этого не происходило в 100% случаях

+0

@usr, что заставляет вас думать, что это не так? –

+0

Он не был нитевым, по крайней мере, до 3.2. Теперь это должно быть, но некоторые «доказательства», например. от документации было бы хорошо :-) – user2126375

+0

Вопрос ясен, я написал его кому-то удаленному комментарию :) –

ответ

3

CreateMap является потокобезопасным. Это не означает, что ваш код вызывает CreateMap. CreateMap следует вызывать только один раз для AppDomain, обычный способ выглядит примерно так: https://github.com/jbogard/ContosoUniversity/blob/master/src/ContosoUniversity/Infrastructure/Mapping/AutoMapperBootstrapper.cs

Карты не должны использовать какие-либо контекстуальные данные, которые передаются через закрытие. Код, который у вас выше, это плохие карты для начала, вы должны вместо этого реорганизовать их для использования встроенных средств для контекстных данных. https://stackoverflow.com/a/31754133/58508

+0

На этом образце я попытался продемонстрировать изменение функции отображения. В реальном проекте это не должно быть проблемой. По-моему, у нас есть CreateMap в нескольких местах (в статических конструкторах классов, которым нужны сопоставления). Причина - лучшее обслуживание (наличие CrtMp близко к месту, где используется это отображение fnct). Только проблемы с этим подходом могут быть в многопоточной среде: один поток вызывает Map, другой вызывает CreateMap. В очень редких случаях (ctrl + c, ctrl + v issue) CreateMap пытается зарегистрировать одни и те же функции отображения в нескольких местах.Поэтому меня интересуют возможные последствия. – user2126375

+0

Класс AutoMapperBootstrapper, связанный с @Jimmy, был реорганизован. Конец, который удалил класс, [здесь] (https://github.com/jbogard/ContosoUniversity/commit/72de8369591ea688597ffb5fe0a5e9811c170d92#diff-094d858ff90f8bce14faec1458d417bf): –

Смежные вопросы