2012-04-05 81 views
2

我有从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(而不是锁),我没有得到这个死锁,但是我再也不知道我是否已经解决了这个问题,或者只是为了测试而修复它案件。
  • 该代码从代码的生产版本中非常精细地删除,所以类中的任何代码看起来都没什么用处,这可能是因为我试图从问题中移除尽可能多的噪音,同时仍然能够重新创建它。

任何人都可以提供任何洞察什么可能导致死锁?我似乎无法指责它。

+0

您需要锁定您的'Parallel.ForEach'锁定不是由我的经验执行的。 – 2012-04-05 02:19:55

+0

@ M.Babcock你能详细说明一下吗? Parallel.Foreach是我用来重现多个用户同时访问Web服务器的问题的一种机制。如果这个foreach存在问题,我可以解决这个问题,但我不认为这是潜在的问题。 – 2012-04-05 02:50:12

+0

没有一个标准的集合对象是完全线程安全的(这对于并发集合也是如此)。您正在使用pinvoke来使用本身的方法,这些方法往往不是线程安全的。您需要包含某种形式的锁定以避免多线程问题。这就是为什么使用'Monitor'锁的原因。 – 2012-04-05 02:54:57

回答

0

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

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 
1

如果我可能会建议你最好的最好的方法是获得悬挂过程的转储&然后使用windbg进行分析。

为了帮助您开始在这里是一个使用WinDbg的检测死锁情况的一个例子

第1步:修复符号路径

.symfix C:\ SOS .reload

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

.load C:\的Windows \ Microsoft.NET \ Framework64 \ V2.0.50727 \ SOS

步骤3:列出加载模块

。链

第4步:检查死锁 - 这会告诉你什么的线程挂起

syncblk

第5步:切换到该线程数 - 在这种情况下,它是#7

〜7

步骤6:名单什么的线程在那个时间做

ķ

第7步:检查是否有任何异常

PE

第8步:获取更详细的线程信息 〜7kL 10

第9步:万一检查堆栈错误

〜* E clrstack

+0

我在几个地方找不到运行时DLL(clr.dll)。我尝试从加载SOS的同一个地方加载它,但仍然出现错误。有任何想法吗? – 2012-04-05 14:12:05

+0

run run .chain - 你看到clr加载了吗? – Johnv2020 2012-04-05 14:48:16

+0

是的,它显示在链中。 – 2012-04-05 15:46:02