【问题标题】:Excel file operations using interop in multithreaded C# application fails在多线程 C# 应用程序中使用互操作的 Excel 文件操作失败
【发布时间】:2015-06-01 06:47:24
【问题描述】:

我有一个应用程序可以自动执行一些与文件相关的工作。每个作业都在单独的线程中执行。一种工作是将 Excel 文件导出为 HTML 格式。为此,我使用 Microsoft.Office.Interop.Excel 命名空间。我的应用程序在 Windows Server 2008 环境下运行良好,但我们将服务器升级到 Windows Server 2012,我开始收到以下错误:

消息过滤器指示应用程序正忙。 (来自 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();
 });

【问题讨论】:

    标签: c# .net multithreading interop export-to-excel


    【解决方案1】:

    我相信 Excel 对象模型是单元线程的,因此来自多个线程的调用将被编组到 Excel 进程中的同一个线程 - 这可能会很忙,尤其是在有多个客户端线程的情况下。

    您可以实现IMessageFilter(OLE 消息过滤器,不要与System.Windows.Forms.IMessageFilter 混淆)来提供自定义重试逻辑。

    您的服务器升级可能改变了时间特性,因此问题发生的频率更高。

    更新

    这是一个 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,尽管它会显示询问用户是否要重试的提示,如果您的客户端是多线程和基于服务器的,这可能不合适。

    【讨论】:

    • 如果出现此错误,我使用重试逻辑重试 5 次但没有帮助,我不断收到错误消息。
    • 如我所说,我认为你应该实现一个 IMessageFilter(Ole 消息过滤器),而不是捕获异常并重试。查看更新。
    • 你能举个例子,我如何在我的代码中使用它。我以自动方式创建线程,我不知道将要处理的下一个作业是否与 excel 相关,所以我认为我无法使用此接口注册所有线程,可以在任务中调用它吗, 就在调用 excel 导出函数之前?
    • 当我尝试调用它的 API 但它很忙时,COM 对象(不是 Excel)是否应该调用 RetryRejectedCall()?我的案例:1)调用API打开项目; 2)用户按Ctrl + C; 3) 调用退出 API。当我调用 Quit 时,我得到 COMException 而不是执行 RetryRejectedCall。但是,当我在 catch 语句中再次调用 Quit for app 时,它会调用 RetryRejectedCall。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-15
    • 2014-08-08
    相关资源
    最近更新 更多