更新2
排队问题可能已经解决了,因为我们仍然面临着并发可能是这种明显行为的原因,但是对于了解VB6中使用的类,模块和变量的内部工作原理有帮助 . 出现一个问题:是否将类中的所有内容(连接,组件等)封装起来,确保每个创建的对象都不与其他实例共享任何数据?
更新1
我们对应用程序进行了多次重构以应对资源处理,尤其是在处理OCX时 . 显然,这解决了内存不足的问题 . 令我困扰的是,我不明白表面下发生了什么 . 在这方面,有没有办法看到当前内存中的对象以及它们有多少引用?我知道引用计数模型与基于垃圾收集器的系统不同 . 我仍然认为RCW包装我们的com对象会让我们保持清洁 . 在给出的模型中,这是一个安全的假设还是我们缺少的东西?
所以,我可能已经阅读了关于COM多线程主题的最多样化的文章和文档,但我仍然无法理解它应该如何工作,特别是在与.Net技术(如ASP.Net MVC)进行交互时 . 这可以被认为是我的一个简单的幻想,除了我们有这个非常关键的项目,我们遇到了严重的问题,试图把一切都联系起来 . 我们正在出现内存错误(在VB6中),显然我们错了如何在COM中创建对象和在这些对象之间共享数据 . 继续阅读以了解故事的发展方向......
事情如何发展
这里不多说 . 我们有一个由许多ActiveX DLL组成的遗留VB6桌面应用程序 . 这些配置为使用 Apartment
作为线程模型,所有类都设置为 MultiUse
. 一切顺利,直到我们被要求在强大的网络上转换应用程序时:O
我们面临的问题以及我们(我们认为)如何解决它
由于我们没有从头开始设计和开发解决方案的资源,因此我们使用基于第三方java(脚本)的框架来快速构建Web应用程序 . 但是,许多实际工作都是由遗留库完成的,因此我们需要一种方法来连接这两个组件 . 我们可以想到的最简单的方法是构建一个非常基本的(没有auth和w / o UI)Asp.Net MVC网站用作中间层 . 这将接收来自Web应用程序的请求,并将它们转换为COM lib以处理数据 .
为此,由于libs从未打算用作服务器,我们尝试重构整个事情,以便现在可以以独立的方式使用大多数类:这包括将逻辑与UI分离并消除所有模块和公共场合尽可能;不幸的是,一些前者仍然存在,特别是一些ComponentOne OCX来处理报告和打印 . 总而言之,这似乎工作得很好,直到我们不得不处理COM线程模型:O
理解废话
长话短说,经过大量的挖掘和头痛,我们设计了当前的解决方案,概述如下:
-
我们像往常一样安装遗留应用程序,以便在注册表中注册其dll;
-
在我们的MVC解决方案中,我们使用
System.Threading.Task
,每个请求一个,以异步方式启动请求的操作 . 我们为操作分配一个id并将此id返回给客户端 . 要启动任务,我们称之为:
protected Task<TReturn> StartSTATask<TReturn>(Func<TReturn> function)
{
var task = Task.Factory.StartNew(
function,
System.Threading.CancellationToken.None,
TaskCreationOptions.None,
STATaskScheduler // property to store the scheduler instance
);
return task;
}
- 使用STATaskScheduler运行任务 . 我们对其进行了修改,以便在池中的线程数设置为0时生成新线程 .
/// <summary>Initializes a new instance of the StaTaskScheduler class with the specified concurrency level.</summary>
/// <param name="numberOfThreads">The number of threads that should be created and used by this scheduler.</param>
public StaTaskScheduler(int numberOfThreads)
{
// Validate arguments
//if (numberOfThreads < 1) throw new ArgumentOutOfRangeException("concurrencyLevel");
// Initialize the tasks collection
_tasks = new BlockingCollection<Task>();
if (numberOfThreads > 0)
{
// Create the threads to be used by this scheduler
_threads = Enumerable.Range(0, numberOfThreads).Select(i =>
{
var thread = new Thread(() =>
{
// Continually get the next task and try to execute it.
// This will continue until the scheduler is disposed and no more tasks remain.
foreach (var t in _tasks.GetConsumingEnumerable())
{
TryExecuteTask(t);
}
});
thread.Name = "sta_thread_" + i;
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
return thread;
}).ToList();
// Start all of the threads
_threads.ForEach(t => t.Start());
}
}
/// <summary>Queues a Task to be executed by this scheduler.</summary>
/// <param name="task">The task to be executed.</param>
protected override void QueueTask(Task task)
{
if (_threads != null)
// Push it into the blocking collection of tasks
_tasks.Add(task);
else
{
var thread = new Thread(() => TryExecuteTask(task));
thread.Name = "sta_thread_task_" + task.Id;
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
}
在我们的基本控制器的 OnActionExecuting
方法中,我们将其初始化
STATaskScheduler = HttpContext.Application["STATaskScheduler"] as TaskScheduler;
if (null == STATaskScheduler)
{
STATaskScheduler = new StaTaskScheduler(0);
HttpContext.Application["STATaskScheduler"] = STATaskScheduler;
}
- 我们使用一个瘦包装器来实例化并通过反射调用我们的COM库:
// Libraries is a Dictionary containing the names of the registered dlls
protected object InitCom(Libraries lib)
{
return InitCom(lib, true);
}
protected virtual object InitCom(Libraries lib, bool setOperation)
{
var comObj = GetComInstance(lib);
var success = SetUpConnection(comObj);
if (!success)
throw new LeafOperationException(lib, "Errore durante la connessione: {1}".Printf(connectionString));
if(setOperation)
return InitOperation(comObj);
return comObj;
}
protected object GetComInstance(Libraries lib)
{
var comType = Type.GetTypeFromProgID(MALib[lib]);
var comObj = Activator.CreateInstance(comType);
return comObj;
}
protected virtual bool DisposeCom(object comObj)
{
var success = CloseConnection(comObj);
if(!success)
throw new LeafOperationException("Errore durante la chiusura della connessione: {1}".Printf(connectionString));
//Marshal.FinalReleaseComObject(comObj);
//comObj = null;
return success;
}
protected bool SetUpConnection(object comObj)
{
var serverName = connectionString.ServerName();
var catalogName = connectionString.CatalogName();
return Convert.ToBoolean(comObj.InvokeMethod("Set_ConnectionWeb", serverName, catalogName));
}
protected bool CloseConnection(object comObj)
{
return Convert.ToBoolean(comObj.InvokeMethod("Close_ConnectionWeb"));
}
protected object InitOperation(object comObj)
{
comObj.GetType().InvokeMember("OperationID", BindingFlags.SetProperty, null, comObj, new object[] { OperationId });
comObj.GetType().InvokeMember("OperationHash", BindingFlags.SetProperty, null, comObj, new object[] { OperationHash });
return comObj;
}
这背后的基本原理是我们使用每个请求创建一个类的新实例,最终在完成时释放它 . 阅读here以了解为什么我们注释了 ReleaseComObject
部分 . 基本上,我们因为很多 COM object that has been separated from its underlying RCW cannot be used
异常而没有内存 .
然后在各种类的方法中使用这个对象:
public bool ChiusuraMese()
{
try
{
PulisciMessaggi();
var comObj = InitCom(Libraries.Chiusura);
var byRefArgs = new int[] { 2 };
var oReturn = comObj.InvokeMethodByRef("ChiusuraMese", byRefArgs, IdDitta, PeriodoGiornaliera, IdDipendenti.PadLeft(), IdGruppoInstallazione, CodGruppoGestione);
DisposeCom(comObj);
return Convert.ToInt32(oReturn) == 0;
}
catch (Exception ex)
{
using (ErrorLog Log = new ErrorLog(System.Reflection.Assembly.GetExecutingAssembly().FullName, ex)) { }
aErrorMessage = ex.Message;
return false;
}
}
其中 InvokeMethodByRef
是以这种方式定义的扩展方法:
public static object InvokeMethodByRef(this object comObj, string methodName, int[] byRefArgs, params object[] args)
{
var modifiers = new ParameterModifier(args.Length);
byRefArgs.ToList().ForEach(index => { modifiers[index] = true; });
return comObj.GetType().InvokeMember(methodName, BindingFlags.InvokeMethod, null, comObj, args, new ParameterModifier[] { modifiers }, null, null);
}
离开公寓
据我所知,这个整个公寓的东西真的很难用,它的跨线程编组,消息循环,yadda yadda诸如此类 . 除此之外,我们正在使用旧的,不受支持的技术来开发一个应用程序,该应用程序没有为我们强制要求的目的而构建 . 所有这一切,并认为.Net方面的事情正常工作,一些想法仍然在我们的脑海中浮现 . 特别是:
-
这是获取COM多线程的正确方法吗?有时,对同一对象的多个请求会像排队一样卡住 . 这让我们想知道COM是否实际上在线程之间共享一些实例;
-
我们是否真的在每个请求中创建和处理对象,或者在引擎盖下COM以不同的方式处理事物?显然,我们可能会出现一些资源争用并重新进入某个我们不希望的地方;
-
设置正确吗?是否有更容易维护和调试的替代方案?请记住,我们不要指望这一点 .
-
在这种项目中使用OCX的方式是什么"least worse"(目前还不是使用它们)?我们应该以某种特定方式处置它们吗?我们已经检查过,在完成后我们将它们设置为空,但也许其他一些线程仍在使用它们;
-
我们应该知道与内存不足问题相关的任何特定COM限制吗?我们在表单显示超过256个唯一控件之前遇到了问题 . 也许在某种程度上发生了同样的事情?该错误似乎与使用UI组件的类特别相关 .
我已经读过的东西(可能还不明白)
在你指向在线资源之前我应该阅读,我在这里添加一些我遇到的主题,按随机顺序:
关于SingleUse / MultiUse
-
http://www.vb-helper.com/howto_activex_dll.html
-
https://msdn.microsoft.com/en-us/library/aa242108(v=vs.60).aspx
如果我们想坚持使用带有表单的ActiveX DLL,那么这里没有太多选择 .
关于(公寓)穿线
-
https://msdn.microsoft.com/en-us/library/aa716297(v=vs.60).aspx
-
https://msdn.microsoft.com/en-us/library/aa716228(v=vs.60).aspx
. 顺便说一句,这个可能暗示对对象的调用被序列化以供其他线程访问 . -
https://msdn.microsoft.com/en-us/library/windows/desktop/ms680112%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
关于调试
-
https://msdn.microsoft.com/en-us/library/aa241684(v=vs.60).aspx
-
https://msdn.microsoft.com/en-us/library/aa716193%28v=vs.60%29.aspx?f=255&MSPPError=-2147217396
当我们遇到错误时,堆栈转储可以提供任何帮助吗?我甚至不知道如何使用WinDbg,所以我想至少知道这是否会浪费时间:D
我们有点被困在这里,因为我们不知道在哪里或者在寻找什么,所以任何一种帮助都会非常感激 .
评论
所以我被指出我应该阅读更多关于COM的线程模型 . 我有点期待 . 无论如何,进一步详细说明,让我写一些评论 .
首先,我对 CoInitialize
没有任何控制权或其他什么,我只是实例化一些VB6 dll . 我猜COM正在做这样的事情 . 事实是,我找不到任何地方( edit - apparently, .Net is already taking care of that for me, see the answer to this question: Do i need to call CoInitialize before interacting with COM in .NET? ) .
回顾一下:
-
我正在使用来自客户端应用程序的STA线程
-
我正在使用
Activator.CreateInstance
,假设它实际上是在每次调用时创建一个新对象 . 该调用在新的STA线程内完成 .
让's set aside for a moment questions about thread-safety in the actual DLLs. What I' m主要感兴趣的是,如果所描述的解决方案是一种正确的方式(可能不是最好的方式,我知道这一点)来利用COM库进行多线程处理 .
引用一些来源,就我目前所知,我应该处于图8.5所示的情况: https://msdn.microsoft.com/en-us/library/aa716228(v=vs.60).aspx
我可以't find any reason why this should not work, since as I said I'假设每个对象都驻留在它自己的公寓中并且有自己的变量,加上一个全局变量的副本(见这里: https://msdn.microsoft.com/en-us/library/aa261361(v=vs.60).aspx
) .