2015-05-31 49 views
0

我有一個應用程序可以自動執行一些與文件相關的作業。每個作業都在不同的線程中執行。一種工作是將Excel文件導出爲HTML格式。爲此我使用Microsoft.Office.Interop.Excel命名空間。我的應用程序在Windows Server 2008環境下工作正常,但我們將服務器升級到Windows Server 2012,並開始出現以下錯誤:在多線程C#應用程序中使用互操作的Excel文件操作失敗

消息過濾器指示應用程序正忙。 (異常來自HRESULT:0x8001010A(RPC_E_SERVERCALL_RETRYLATER))

事情是導出函數第一次調用成功出口Excel文件HTML,但後續的調用失敗,出現上述錯誤。我確保關閉並最終確定所有與Excel相關的對象,並從任務管理器中檢查excel.exe是否無效,但沒有運氣。

我用下面的代碼,如果發生這個錯誤,但它不斷變得異常和試5次後

while (!success) 
      { 
try 
       { 
        ExportExcel(); 
        success = true; 
        System.Threading.Thread.Sleep(2000); 
       } 
       catch (System.Runtime.InteropServices.COMException loE) 
       { 
        tryCount++; 
        if (loE.HResult.ToString("X") == "80010001" || loE.HResult.ToString("X") == "8001010A" && tryCount<5) 
        {                  
         System.Threading.Thread.Sleep(2000); 
        } 
        else 
        { 
         throw; 
        } 
       } 
      } 

我懷疑這可能是一個相關的一些線程錯誤,但我不能拿出未能重試與一個答案。任何見解都會有所幫助。

謝謝喬指出正確的方式:

我結束了使用具有以下鏈接的混合的解決方案: http://blogs.artinsoft.net/Mrojas/archive/2012/09/28/Office-Interop-and-Call-was-rejected-by-callee.aspx

http://blogs.msdn.com/b/pfxteam/archive/2010/04/07/9990421.aspx

所以我用類似如下:

StaTaskScheduler cts=new StaTaskScheduler(1); 
TaskFactory factory;   
factory = new TaskFactory(cts); 
Task jobRunTask = factory.StartNew(() => 
{ 
    MessageFilter.Register(); 
    ExcelInteropFunction(); 
    MessageFilter.Revove(); 
}); 

回答

1

我相信Excel對象模型是公寓t因此來自多個線程的調用將被封送到Excel進程中的相同線程 - 可能會很忙,尤其是在有多個客戶端線程的情況下。

您可以實施IMessageFilter(OLE消息過濾器,不要與System.Windows.Forms.IMessageFilter混淆)來提供自定義重試邏輯。

您的服務器升級可能會更改計時特性,以便更頻繁地出現問題。

UPDATE

下面是一個簡單基本實現了OLE消息過濾器:

// Definition of the IMessageFilter interface which we need to implement and 
    // register with the CoRegisterMessageFilter API. 
    [ComImport(), Guid("00000016-0000-0000-C000-000000000046"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] 
    interface IOleMessageFilter // Renamed to avoid confusion w/ System.Windows.Forms.IMessageFilter 
    { 
     [PreserveSig] 
     int HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo); 
     [PreserveSig] 
     int RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType); 
     [PreserveSig] 
     int MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType); 
    } 

    internal sealed class OleMessageFilter : IOleMessageFilter, IDisposable 
    { 
     [DllImport("ole32.dll")] 
     private static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, out IOleMessageFilter oldFilter); 

     private bool _isRegistered; 
     private IOleMessageFilter _oldFilter; 

     public OleMessageFilter() 
     { 
      Register(); 
     } 

     private void Register() 
     { 
      // CoRegisterMessageFilter is only supported on an STA thread. This will throw an exception 
      // if we can't switch to STA 
      Thread.CurrentThread.SetApartmentState(ApartmentState.STA); 

      int result = CoRegisterMessageFilter(this, out _oldFilter); 
      if (result != 0) 
      { 
       throw new COMException("CoRegisterMessageFilter failed", result); 
      } 
      _isRegistered = true; 
     } 

     private void Revoke() 
     { 
      if (_isRegistered) 
      { 
       IOleMessageFilter revokedFilter; 
       CoRegisterMessageFilter(_oldFilter, out revokedFilter); 
       _oldFilter = null; 
       _isRegistered = false; 
      } 
     } 

     #region IDisposable Members 

     private void Dispose(bool disposing) 
     { 
      if (disposing) 
      { 
       // Dispose managed resources 
      } 
      // Dispose unmanaged resources 
      Revoke(); 
     } 

     void IDisposable.Dispose() 
     { 
      GC.SuppressFinalize(this); 
      Dispose(true); 
     } 

     ~OleMessageFilter() 
     { 
      Dispose(false); 
     } 

     #endregion 

     #region IOleMessageFilter Members 

     int IOleMessageFilter.HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo) 
     { 
      return 0; //SERVERCALL_ISHANDLED 
     } 

     int IOleMessageFilter.RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType) 
     { 
      if (dwRejectType == 2) // SERVERCALL_RETRYLATER 
      { 
       return 200; // wait 200ms and try again 
      } 

      return -1; // cancel call 
     } 

     int IOleMessageFilter.MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType) 
     { 
      return 2; //PENDINGMSG_WAITDEFPROCESS 
     } 
     #endregion 
    } 

你也可以看看this sample,雖然它會顯示一個提示,詢問用戶是否要重試如果你的客戶端是多線程的並且基於服務器的話,這可能是不合適的。

+0

我使用重試邏輯重試5次,如果發生此錯誤,但它沒有幫助,我不斷收到錯誤。 – erdem

+0

正如我所說,我認爲你應該實現一個IMessageFilter(Ole消息過濾器),而不是捕獲異常並重試。查看更新。 – Joe

+0

你能舉一個例子說明如何在代碼中使用它。 我以自動方式創建我的線程,我不知道下一個將要處理的工作是否與excel相關,因此我認爲我無法使用此接口註冊所有線程,是否可以在內部調用它一個任務,就在excel導出函數調用之前? – erdem