首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在Convert.ChangeType中使用Parallel.ForEach死锁

在Convert.ChangeType中使用Parallel.ForEach死锁
EN

Stack Overflow用户
提问于 2019-02-14 17:31:18
回答 2查看 300关注 0票数 2

我只需要调试带有死锁的代码,但我真的找不到原因。详细地说,死锁发生在调用Convert.ChangeType循环中的Parallel.ForEach时。

我试图找到任何关于这个方法的线程安全的信息,但是我找不到一些。所以我看了一下.NET源代码,并尝试做他们做的事情,所以我不需要调用Convert.ChangeType。最后,代码在没有死锁的情况下运行。

在我的示例代码中,我将枚举类型转换为它的下置ulong类型:

代码语言:javascript
复制
public class TestClass<T> where T : struct, IConvertible
{
    private static readonly Type uLongType = typeof(ulong);
    public static readonly TestClass<T> Instance = new TestClass<T>();

    private readonly Dictionary<string, object> _NumericValues = new Dictionary<string, object>();
    private readonly Dictionary<string, T> _Values = new Dictionary<string, T>();

    public TestClass()
    {
        if (!typeof(T).IsEnum) throw new InvalidOperationException("Enumeration type required");
        Type t = typeof(T);
        foreach (T value in Enum.GetValues(t)) _Values[Enum.GetName(t, value)] = value;
        // Deadlock at Convert.ChangeType
        Parallel.ForEach(ValueNames, new Action<string>((key) =>
        {
            object value = Convert.ChangeType(_Values[key], uLongType);
            lock (_NumericValues) _NumericValues[key] = value;
            // In real life here comes a lot more code...
        }));
        // Works!
        Parallel.ForEach(ValueNames, new Action<string>((key) =>
        {
            object value = ((IConvertible)_Values[key]).ToUInt64(null);
            lock (_NumericValues) _NumericValues[key] = value;
        }));
    }

    public string[] ValueNames => new List<string>(_Values.Keys).ToArray();
}

public enum TestEnum : ulong
{
    Value1,
    Value2,
    Value3
}

复制f.e.:

代码语言:javascript
复制
System.Diagnostics.Debug.WriteLine(TestClass<TestEnum>.Instance.ValueNames.Length);

但我真的不明白,为什么Convert.ChangeType会造成僵局--有人知道吗?

编辑:它适用于Convert.ChangeType,如果我在静态构造函数中初始化Instance --但是为什么呢?

代码语言:javascript
复制
    public static readonly TestClass<T> Instance = null;

    static TestClass()
    {
        Instance = new TestClass<T>();
    }
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2019-02-15 09:26:36

我认为问题纯粹在于您在类型初始化器中执行阻塞操作。CLR必须在锁内运行类型初始化器,因为它必须防止它们被运行两次,并且它对所有类型都使用相同的锁。如果在类型初始化器中执行线程处理,然后阻塞,则可能会发生死锁。

我认为这正是这里所发生的事情:

  1. 主线程捕获类型初始化程序锁并运行类型初始化程序。
  2. 生成另一个线程,它访问Convert类,该类需要运行其类型初始化程序。因此,它试图获取类型初始化程序锁。
  3. 主线程阻塞等待第二个线程完成,保存类型初始化程序锁。
  4. 死锁

在直接调用IConvertable.ToUInt64时没有看到这一点,因为这不需要调用Convert类的类型初始化器。

当您的TestClass<T>.Instance被指定为内联时,将设置BeforeFieldInit标志。这意味着CLR使用了一种轻松的方法来运行类型初始化程序,在我的测试中,它在Main之前、Convert的类型初始化程序运行之前运行。在定义显式静态构造函数时,当TestClass<T>.Instance首次在Main中引用时,CLR被迫运行类型初始化器,这可能是在Convert初始化之后,这是为了避免死锁。

这方面的证据是了解类型初始化器是如何运行的,线程在运行时中的某个位置阻塞(但在它有机会运行Convert.ChangeType方法之前),以及仅仅引用Convert类型就足以触发这个事实。

这篇MSDN文章。我认为需要注意的是,您可能不应该在您的类型初始化器中执行线程,而且您肯定不应该阻塞正在运行类型初始化程序的线程。

我很乐意考虑您实际的(非简化的)问题,并尝试一个改进其性能的建议方法,而不用使用类型初始化器中的线程。

票数 1
EN

Stack Overflow用户

发布于 2019-02-15 10:29:30

原因与Convert.ChangeType无关,它恰好显示了这个问题,因为调用引用了静态uLongType字段,这会导致TestClass<T>类型初始化程序运行。

真正的罪魁祸首是静态Instance字段,它创建了一个新的TestClass<T>实例。这会造成潜在的死锁,因为类型初始化程序要求实例构造函数完成,但是实例构造函数正在等待多个线程,而多个线程则等待类型初始化器完成。

添加一个静态构造函数,它删除beforefieldinit类型属性并更改注释中提到的类型初始化行为,在我的测试中,只有半可靠地隐藏死锁和附加调试器。这并不能真正解决这个问题。

下面是一个简化的示例,它大部分时间都显示了这个问题:

代码语言:javascript
复制
static void Main()
{
    new TestClass();
    Console.WriteLine("Not deadlocked");
}

public class TestClass
{
    static Type uLongType = typeof(ulong);
    static TestClass Instance = new TestClass();

    static TestClass() { }

    public TestClass()
    {
        var values = Enumerable.Range(0, 20).ToList();

        Parallel.ForEach(values, (value) =>
        {
            uLongType.ToString();

            //Forcing the lambda to be compiled as an instance method
            //changes the behavior but deadlocks can happen either way
            InstanceMethod();
        });
    }

    void InstanceMethod() { }
}

死锁概率取决于lambda中实例和/或静态使用的组合、附加调试器、发布优化、静态构造函数、lambda中的Console.WriteLine调用和随机Parallel线程调度,但这种情况总是可能发生的。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/54696040

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档