2015-05-31 47 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