我有从web应用程序使用win32 api的代码。我在ASP.Net开发服务器上运行这段代码时遇到了一个死锁(我无法在IIS中重现,但我不知道在某些情况下不会发生这种情况)。下面是我已经下调仍然重现问题的一类:ASP.Net开发服务器死锁
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(而不是锁),我没有得到这个死锁,但是我再也不知道我是否已经解决了这个问题,或者只是为了测试而修复它案件。
- 该代码从代码的生产版本中非常精细地删除,所以类中的任何代码看起来都没什么用处,这可能是因为我试图从问题中移除尽可能多的噪音,同时仍然能够重新创建它。
任何人都可以提供任何洞察什么可能导致死锁?我似乎无法指责它。
您需要锁定您的'Parallel.ForEach'锁定不是由我的经验执行的。 – 2012-04-05 02:19:55
@ M.Babcock你能详细说明一下吗? Parallel.Foreach是我用来重现多个用户同时访问Web服务器的问题的一种机制。如果这个foreach存在问题,我可以解决这个问题,但我不认为这是潜在的问题。 – 2012-04-05 02:50:12
没有一个标准的集合对象是完全线程安全的(这对于并发集合也是如此)。您正在使用pinvoke来使用本身的方法,这些方法往往不是线程安全的。您需要包含某种形式的锁定以避免多线程问题。这就是为什么使用'Monitor'锁的原因。 – 2012-04-05 02:54:57