c# - 在多线程C#应用程序中使用互操作的Excel文件操作失败

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

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

The message filter indicated that the application is busy. (Exception from 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();
 });

最佳答案

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

关于c# - 在多线程C#应用程序中使用互操作的Excel文件操作失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30561541/

相关文章:

c# - 防止 Thread.CurrentPrincipal 跨应用程序域传播

c# - TypeMock 中的 [TestMethod] 属性在哪里?

c++ - 如何使用 QMutex?

c# - async/await - 我使用了错误的同步上下文吗?

c# - WPF Application是否需要继承System.Windows.Application基类?

c# - VBA C# DLL 未注册

c# - 是否可以运行在 sql server 2008 上构建的应用程序以与 2005 一起运行

c# - 如何在文件的一部分上使用 DeflateStream?

c# - Azure 服务总线 "send"抛出 由于对象的当前状态,操作无效

c++ - 是否曾在大型多线程 C++ 程序中使用过通信顺序进程?