我有從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