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);
}
}
}
次のコードを使用して、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.");
}
Win764ビットとVS2010SP1の両方を実行している2つの異なるマシンでこれを再現しました。私の出力では、次のようなものが表示されます。
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を実行すると、デッドロックは発生しませんが、問題が解決したか、テストケース用に修正したかはわかりません。
- このコードは、製品版のコードから大幅に削減されているため、クラス内のコードがあまり機能していないように見えるのは、問題を再現しながら、問題からできるだけ多くのノイズを取り除こうとしたためと考えられます。
デッドロックの原因となっている可能性のあるものについて、誰かが洞察を提供できますか?指が上がらないようです。