ASP.Net开发服务器死锁

本文关键字:死锁 服务器 开发 Net ASP | 更新日期: 2023-09-27 18:29:58

我有一段代码正在使用web应用程序中的win32api。当我在ASP.Net开发服务器中运行此代码时,我遇到了死锁(我无法在IIS中复制,但我不知道在某些情况下不会发生这种情况)。以下是我修剪过的一个类,它仍然再现了这个问题:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Runtime.InteropServices;
namespace Web_ShellIconBug
{
    public class IconIndexClass
    {
        [StructLayout(LayoutKind.Sequential)]
        private struct SHFILEINFO
        {
            public IntPtr hIcon;
            public int iIcon;
            public int dwAttributes;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            public string szDisplayName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
            public string szTypeName;
        }
        [DllImport("shell32", CharSet = CharSet.Unicode)]
        private static extern IntPtr SHGetFileInfo(string pszPath, int dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);
        private static object m_lock = new object();
        public int IconIndex(
            string fileName,
            bool tryDisk,
            int iconState
            )
        {
            // On some machines, you might need this to make sure multiple threads are spawned
            //System.Threading.Thread.Sleep(100);
            SHFILEINFO shfi = new SHFILEINFO();
            IntPtr retVal;
            uint shfiSize = (uint)Marshal.SizeOf(shfi.GetType());
            MyLog("Before Lock.");
            lock (m_lock)
            {
                MyLog("Obtained Lock.");
                retVal = SHGetFileInfo(fileName, 0, ref shfi, shfiSize, 0);
            }
            MyLog("Lock released.");
            if (retVal.Equals(IntPtr.Zero))
            {
                MyLog("IntPtr is zero");
                if (tryDisk)
                {
                    if (System.IO.Directory.Exists(fileName))
                        return IconIndex(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), false, iconState);
                    else return IconIndex(fileName, false, iconState);
                }
                else
                    return 0;
            }
            else
            {
                return shfi.iIcon;
            }
        }
        private void MyLog(string val)
        {
            System.Diagnostics.Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss.ffff") + " - Thread:" + System.Threading.Thread.CurrentThread.ManagedThreadId + " - Msg:" + val);
        }
    }
}

我可以使用以下代码在网络应用程序中重现错误:

protected void Page_Load(object sender, EventArgs e)
{
    Web_ShellIconBug.IconIndexClass ii = new Web_ShellIconBug.IconIndexClass();
    Parallel.ForEach(System.IO.Directory.GetFiles("C:''Windows"), file =>
    {
        ii.IconIndex(file, false, 0);
    });
    Debug.WriteLine("Done.");
}

我在两台不同的机器上复制了这一点,这两台机器都运行Win7 64位和VS2010SP1。在我的输出中,我会看到这样的东西:

21:39:01.7812 - Thread:5 - Msg:Before Lock.
21:39:01.7912 - Thread:5 - Msg:Obtained Lock.
21:39:01.8022 - Thread:5 - Msg:Lock released.
21:39:01.8162 - Thread:10 - Msg:Before Lock.
21:39:02.8382 - Thread:11 - Msg:Before Lock.
21:39:03.8172 - Thread:12 - Msg:Before Lock.
21:39:04.3032 - Thread:5 - Msg:Before Lock.
21:39:04.3032 - Thread:5 - Msg:Obtained Lock.
21:39:04.3042 - Thread:5 - Msg:Lock released.
21:39:04.8162 - Thread:13 - Msg:Before Lock.
...

在这种情况下,看起来线程5正在获得锁,但没有释放它,所以所有其他线程都被无限期地阻塞。

需要注意的其他几点:

  • 再现僵局相当棘手。如果我在检查返回值是否等于IntPtr.Zero之后修改任何递归调用,死锁似乎会消失,但我不明白为什么这会影响任何锁定,所以我不太愿意说修改代码可以纠正问题
  • 如果我手动执行Monitor.Enter和Monitor.Exit(而不是锁定),我不会出现死锁,但我也不确定我是否已经解决了问题,或者只是为我的测试用例修复了它
  • 这段代码是从生产版本的代码中精简而来的,所以类中任何看起来没有多大作用的代码都可能是因为我试图从问题中消除尽可能多的噪声,同时仍然能够重新创建它

有人能深入了解可能导致死锁的原因吗?我似乎无法用手指触摸它。

ASP.Net开发服务器死锁

如果我可以建议你最好的方法是转储挂起过程&然后利用windpg进行分析。

为了帮助您开始,这里有一个使用windbg检测死锁场景的示例

步骤1:修复符号路径

.symfix c:''sos.重新加载

步骤2:加载sos-只需加载你正在使用的任何版本的.net

.load C:''Windows''Microsoft.NET''Framework64''v2.0.50727''sos

步骤3:列出加载的模块

.链

步骤4:检查死锁-这将告诉你哪个线程挂起了

syncblk

步骤5:切换到线程号-在这种情况下,它是#7

~7

步骤6:列出线程当时在做什么

k

步骤7:检查是否有任何异常

pe

步骤8:获取有关线程的更多详细信息~7kL 10

步骤9:以防万一检查堆栈中的错误

~*e!clrstack

事实证明这是StructLayout的问题。我们在函数上指定了Unicode,但在结构上没有指定,所以它默认为ANSI。正确的结构布局是:

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]