2

我有使用来自网络应用程序的 win32 api 的代码。当我在 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);
        }
    }
}

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

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.");
}

我已经在运行 Win 7 64 位和 VS 2010 SP1 的两台不同的机器上重现了这个。在我的输出中,我将看到如下内容:

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 (而不是锁定),我不会遇到死锁,但同样,我不确定我是否已经解决了问题,或者只是为我的测试用例修复了它。
  • 这段代码与代码的生产版本相比非常精简,因此该类中任何看起来没有多大作用的代码可能是因为我试图从问题中消除尽可能多的噪音,同时仍然能够重新创建它。

任何人都可以提供任何可能导致僵局的见解吗?我似乎无法将手指放在它上面。

4

2 回答 2

1

如果我可以建议您最好的方法是获取挂起过程的转储,然后使用 windbg 进行分析。

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

第 1 步:修复符号路径

.symfix c:\sos .reload

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

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

第三步:列出加载的模块

。链

第 4 步:检查死锁 - 这将告诉您哪个线程挂起

同步块

第 5 步:切换到该线程号 - 在本例中为 #7

~7

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

ķ

第 7 步:检查任何异常

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

第 9 步:以防万一检查堆栈是否有错误

~* e !clrstack

于 2012-04-05T12:43:16.747 回答
0

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

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
于 2012-04-27T18:45:56.407 回答