我的代码使用网络应用程序中的 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);
}
}
}
我可以使用以下代码在网络应用程序中重现错误:
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(而不是锁定),我不会遇到死锁,但同样,我不确定我是否已经解决了问题或只是为了我的测试修复了它案例。
- 此代码已从代码的生产版本中精简了很多,因此类中任何看起来没有做太多事情的代码可能是因为我试图尽可能多地从问题中消除噪音,同时仍然能够重新创建
任何人都可以深入了解可能导致僵局的原因吗?我似乎不能把我的手指放在上面。
最佳答案
如果我可以建议你在这里最好的是获取挂起进程的转储,然后使用 windbg 进行分析。
为了帮助您入门,这里有一个使用 windbg 检测死锁情况的示例
第一步:修复符号路径
.symfix c:\sos .reload
第 2 步:加载 sos - 只需加载您正在使用的任何版本的 .net
.load C:\Windows\Microsoft.NET\Framework64\v2.0.50727\sos
第三步:列出加载的模块
.chain
第 4 步:检查死锁 - 这将告诉您哪个线程已挂起
同步块(synchronized block)
第 5 步:切换到该线程号 - 在本例中为 # 7
~7
第六步:列出当时线程正在做什么
k
第 7 步:检查是否有任何异常
!pe
Step 8: get more detailed info on the thread ~7kL 10
第 9 步:以防万一检查堆栈是否有错误
~* e !clrstack
关于c# - ASP.Net 开发服务器死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10021717/