[英]Excel file operations using interop in multithreaded C# application fails
我有一個可以自動執行一些文件相關作業的應用程序。 每個作業都在單獨的線程中執行。 一種工作是將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();
});
我相信Excel對象模型是單元線程的,因此來自您多個線程的調用將被編組到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
}
您還可以查看此示例 ,盡管它顯示提示詢問用戶是否要重試,如果您的客戶端是多線程且基於服務器的,則可能不合適。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.