简体   繁体   English

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

[英]Excel file operations using interop in multithreaded C# application fails

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. 一种工作是将Excel文件导出为HTML格式。 I use Microsoft.Office.Interop.Excel namespace for this purpose. 为此,我使用Microsoft.Office.Interop.Excel命名空间。 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 : 我的应用程序在Windows Server 2008环境下运行良好,但是我们将服务器升级到Windows Server 2012,然后开始出现以下错误:

The message filter indicated that the application is busy. 消息过滤器指示应用程序正忙。 (Exception from HRESULT: 0x8001010A (RPC_E_SERVERCALL_RETRYLATER)) (来自HRESULT的异常:0x8001010A(RPC_E_SERVERCALL_RETRYLATER))

The thing is first call to export function successfully exports Excel file to HTML but successive calls fails with the above error. 事情是第一次调用导出功能成功将Excel文件导出到HTML,但是连续调用失败并出现上述错误。 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. 我确保关闭并完成所有与Excel相关的对象,并从任务管理器中检查excel.exe不能正常运行,但没有运气。

I use the following code to retry if this error occurs but it keeps getting the exception and fails after 5 retries 如果发生此错误,我将使用以下代码重试,但它会不断获取异常,并在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;
                    }
                }
             }

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: 谢谢乔指出正确的方法:

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.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 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();
 });

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

You can implement IMessageFilter (OLE message filter, not to be confused with System.Windows.Forms.IMessageFilter ) to provide custom retry logic. 您可以实现IMessageFilter (OLE消息筛选器,不要与System.Windows.Forms.IMessageFilter混淆)以提供自定义重试逻辑。

Your server upgrade might have changed the timing characteristics so that the problem occurs more frequently. 您的服务器升级可能已经更改了时间特性,因此该问题更加频繁地发生。

UPDATE UPDATE

Here is a sample basic implementation of an OLE message filter: 这是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
    }

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. 您还可以查看此示例 ,尽管它显示提示询问用户是否要重试,如​​果您的客户端是多线程且基于服务器的,则可能不合适。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM