2012-04-05 79 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