In our current project, we register mappings in static class constructors that are called by multiple threads. Mappings in static constructors apply only to these classes. But still, several CreateMap calls can be launched at the same time. In addition, sometimes (mainly as copies / past releases) the same mappings can be registered in static constructors of different classes.
I tried google whether Mapper.CreateMap is thread safe or not. And I found only the following:
In the message Is Mapper.Map has been streaming AutoMapper since 2012, in nemesv there is a note that CreateMap is not thread safe and it will never be.
But I found a problem in GitHub Static APIs DynamicMap and CreateMap should be thread safe since 2014, marked as closed in version 3.2. This suggests that CreateMap should now be thread safe.
Can you confirm that CreateMap is thread safe? I did some tests and it seems like everything should be, but if someone with deeper knowledge can confirm this information, everything will be fine.
EDIT After some additional testing, the CreateMap behavior seems very interesting:
I used the following code for testing
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; } }
In all my tests, when the first Map method is called, this means that CreateMap is frozen and no more changes to the mapping function can be made (distinctAges1 was always the only unique value, and one value was in distinctAges2). Changing the display function from two streams sometimes leads to an increase in alternating Age values ββfrom a negative to a positive number (tests end with a high value of different ages). But sometimes the behavior was completely different, and the iteration of age stopped at a value of 1 or -1. There seem to be some internal mechanisms for freezing changes in the display function if this mapping function changes from more threads. But this did not happen in 100% of cases