Automapper Mapper.CreateMap 是线程安全的吗?
Is Automapper Mapper.CreateMap thread-safe?
在我们当前的项目中,我们正在 类 的静态构造函数中注册映射,这些构造函数被多个线程调用。静态构造函数中的映射仅与 类 相关。但是仍然可以同时多次调用 CreateMap 运行。此外偶尔(主要是 copy/past 问题)相同的映射可以在不同 类.
的静态构造函数中注册
我试过 google 是否 Mapper.CreateMap 是线程安全的。我发现只有以下内容:
在 2012 年的 post Is Mapper.Map in AutoMapper thread-safe 中,nemesv 的回答中指出 CreateMap 不是线程安全的,而且永远不会。
但我在 GitHub Static DynamicMap and CreateMap APIs should be thread-safe 上发现了一个问题,该问题从 2014 年开始在 3.2 版本中标记为已关闭。这表明 CreateMap 现在应该是线程安全的。
你能确认CreateMap是线程安全的吗?我 运行 进行了一些测试,看起来应该如此,但如果有更深入知识的人可以确认此信息,那就没问题了。
编辑
经过一些额外的测试后,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; }
}
在我的所有测试中,当第一个 Map 方法被调用时,这意味着 CreateMap 被冻结并且无法对映射函数进行更多更改(distinctAges1 始终是一个唯一值,并且相同的值在 distinctAges2 中)。从两个线程更改映射函数有时会导致 Age 的交替值从负数增加到正数(测试以不同 Age 的高值结束)。但有时行为完全不同,Age 迭代在值 1 或 -1 处停止。如果此映射函数从更多线程更改,则似乎有一些内部机制冻结了对映射函数的更改。但这并非发生在 100% 的情况下
CreateMap 是线程安全的。这并不意味着您调用 CreateMap 的代码是。每个 AppDomain 只应调用 CreateMap 一次,通常的方法是这样的:https://github.com/jbogard/ContosoUniversity/blob/master/src/ContosoUniversity/Infrastructure/Mapping/AutoMapperBootstrapper.cs
地图不应使用通过闭包传入的任何上下文数据。您上面的代码一开始就是糟糕的地图,您应该重构它们以使用内置的方式获取上下文数据
在我们当前的项目中,我们正在 类 的静态构造函数中注册映射,这些构造函数被多个线程调用。静态构造函数中的映射仅与 类 相关。但是仍然可以同时多次调用 CreateMap 运行。此外偶尔(主要是 copy/past 问题)相同的映射可以在不同 类.
的静态构造函数中注册我试过 google 是否 Mapper.CreateMap 是线程安全的。我发现只有以下内容:
在 2012 年的 post Is Mapper.Map in AutoMapper thread-safe 中,nemesv 的回答中指出 CreateMap 不是线程安全的,而且永远不会。
但我在 GitHub Static DynamicMap and CreateMap APIs should be thread-safe 上发现了一个问题,该问题从 2014 年开始在 3.2 版本中标记为已关闭。这表明 CreateMap 现在应该是线程安全的。
你能确认CreateMap是线程安全的吗?我 运行 进行了一些测试,看起来应该如此,但如果有更深入知识的人可以确认此信息,那就没问题了。
编辑 经过一些额外的测试后,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; }
}
在我的所有测试中,当第一个 Map 方法被调用时,这意味着 CreateMap 被冻结并且无法对映射函数进行更多更改(distinctAges1 始终是一个唯一值,并且相同的值在 distinctAges2 中)。从两个线程更改映射函数有时会导致 Age 的交替值从负数增加到正数(测试以不同 Age 的高值结束)。但有时行为完全不同,Age 迭代在值 1 或 -1 处停止。如果此映射函数从更多线程更改,则似乎有一些内部机制冻结了对映射函数的更改。但这并非发生在 100% 的情况下
CreateMap 是线程安全的。这并不意味着您调用 CreateMap 的代码是。每个 AppDomain 只应调用 CreateMap 一次,通常的方法是这样的:https://github.com/jbogard/ContosoUniversity/blob/master/src/ContosoUniversity/Infrastructure/Mapping/AutoMapperBootstrapper.cs
地图不应使用通过闭包传入的任何上下文数据。您上面的代码一开始就是糟糕的地图,您应该重构它们以使用内置的方式获取上下文数据