在多线程C#应用程序中使用互操作的Excel文件操作失败 [英] Excel file operations using interop in multithreaded C# application fails

查看:157
本文介绍了在多线程C#应用程序中使用互操作的Excel文件操作失败的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I've an application that automates some file related jobs. Every job is executed inside separate threads. One kind of job is exporting an Excel file to HTML format. I use Microsoft.Office.Interop.Excel namespace for this purpose. My application was working fine under Windows Server 2008 environment but we upgraded our server to Windows Server 2012 and I started to get the following error :

消息过滤器指示应用程序正忙. (来自HRESULT的异常:0x8001010A(RPC_E_SERVERCALL_RETRYLATER))

The message filter indicated that the application is busy. (Exception from HRESULT: 0x8001010A (RPC_E_SERVERCALL_RETRYLATER))

事情是第一次调用导出功能成功将Excel文件导出到HTML,但是连续调用失败并出现上述错误.我确保关闭并完成所有与Excel相关的对象,并从任务管理器中检查excel.exe不能正常运行,但没有运气.

The thing is first call to export function successfully exports Excel file to HTML but successive calls fails with the above error. I make sure to close and finalize all my Excel related objects and check from task manager that excel.exe is not working but with no luck.

如果发生此错误,我将使用以下代码重试,但它会不断获取异常,并在5次重试后失败

I use the following code to retry if this error occurs but it keeps getting the exception and fails after 5 retries

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;
                    }
                }
             }

我怀疑这可能与某些线程错误有关,但我无法给出答案.任何见解都会有所帮助.

I suspect this might be something related some threading error but I can't come up with an answer. Any insight would be helpful.

感谢乔指出正确的方法:

Thank you Joe for pointing out the right way:

我最终使用了包含以下链接的解决方案: http ://blogs.artinsoft.net/Mrojas/archive/2012/09/28/Office-Interop-and-Call-was-rejected-by-callee.aspx

I ended up using a solution with a mixture of the following links: 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

所以我使用了以下内容:

So I used something like the following:

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

推荐答案

我相信Excel对象模型是单元线程的,因此来自您多个线程的调用将在Excel进程中被封送到同一线程-特别是可能很忙如果有多个客户端线程.

I believe the Excel object model is apartment threaded, so calls from your multiple threads will be marshaled to the same thread in the Excel process - which may be busy especially if there are several client threads.

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

You can implement IMessageFilter (OLE message filter, not to be confused with System.Windows.Forms.IMessageFilter) to provide custom retry logic.

您的服务器升级可能已经更改了计时特性,因此该问题更加频繁地发生.

Your server upgrade might have changed the timing characteristics so that the problem occurs more frequently.

更新

这是OLE消息过滤器的示例基本实现:

Here is a sample basic implementation of an OLE message filter:

    // 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
    }

您还可以查看此示例,尽管它会显示提示询问用户是否要重试,但如果您的客户端是多线程且基于服务器的,则可能不合适.

You can also look at this sample, though it displays a prompt asking the user if they want to retry, which is probably not appropriate if your client is multithreaded and server-based.

这篇关于在多线程C#应用程序中使用互操作的Excel文件操作失败的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆