我有一个遗留的VB6应用程序,其中我们创建一个 Task
对象来执行我们收到事件后完成的工作,然后我订阅了这个任务的事件,最后我 WaitOne
在 AutoResetEvent
我用来控制线程 . 我还将任务 ContinueWith
设置为 Set
AutoResetEvent
.
当我使用控制台应用程序(用.NET编写)测试此设置时,我没有一个问题 . 但是,当我通过VB6调用相同的方法时,有时它会工作,有时它不会,并且只会无限期地挂起等待该事件触发 .
我一直在使用WinDbg分开它,我注意到所有挂起的尝试,都会抛出访问冲突 . 我正在试图找出当前这种访问冲突的来源,但是在.NET代码中我们调用事件委托(如果不是null)的那一点是正确的 .
以下是来自WinDbg的示例:
实例化对象>>>启动事件线程>>>订阅OnCreateCtiConf >>>等待任务结果 . >>>排队OnCreateCTIConf:无消息(1e44.1c4c):访问冲突 - 代码c0000005(第一次机会)在任何异常处理之前报告第一次机会异常 . 可以预期和处理此异常 . eax = 0a61e9d0 ebx = 0a65bccc ecx = 00000000 edx = 0a7057a4 esi = 0a61e9d0 edi = 0caef804 eip = 091249a4 esp = 0caef7e0 ebp = 0caef810 iopl = 0 nv up ei pl nz na po nc cs = 001b ss = 0023 ds = 0023 es = 0023 fs = 003b gs = 0000 efl = 00010202 091249a4 3909 cmp dword ptr [ecx],ecx ds:0023:00000000 = ????????
这是我们调用的方法有时会挂起:
public bool AgentLogin(string extension, string agentId, string password)
{
_extension = extension;
_agentId = agentId;
_eventQueue = new Queue<AvayaEvent>();
try
{
_phone = new CTISoftphone($"ext={extension},logFile={Settings.LogLocation}");
}
catch (Exception ex)
{
addEvent(new AvayaEvent(CTISoftphone.CTIEvent.OnError, $"{_wrapperLogPrefix} Unable to create CTISoftPhone. The error was: {ex.Message}"));
return false;
}
_phone.PhoneEvent += new CTISoftphone.PhoneEventHandler((evnt, msg) =>
{
Trace.WriteLine($">>> Enqueued {evnt} : {(string.IsNullOrEmpty(msg) ? "No message" : msg)}");
addEvent(new AvayaEvent(evnt, msg));
});
Trace.WriteLine(">>> Starting events thread");
t_avayaEvents.Start();
var are = new AutoResetEvent(false);
var task = new Task(() =>
{
try
{
Trace.WriteLine(">>> Calling LoginAgent");
_phone.LoginAgent(agentId, password);
}
catch (Exception)
{
}
});
task.ContinueWith((t) => are.Set());
var task_handler = new OnCreateCtiConfHandler(task.Start);
Trace.WriteLine(">>> Subscribing to OnCreateCtiConf");
OnCreateCtiConf += task_handler;
Trace.WriteLine(">>> Waiting for task result.");
are.WaitOne();
Trace.WriteLine(">>> Unsubscribing from OnCreateCtiConf");
OnCreateCtiConf -= task_handler;
return true;
}
我们最初认为在我们订阅公开的 PhoneEvent
之前错过了事件,因此事件队列循环线程 . 基本上我们将 AvayaEvent
添加到 Queue<T>
,并且一个单独的线程处理这些事件 . 如果有一个委托,它会触发并忘记,否则,该事件将被重新排队 . 作为参考,我们在实现事件队列之前仍然遇到了这个问题 .
对于COM互操作,我们做了一些事情:
-
已为
Phone
对象及其事件(成员和事件的单独接口)创建了接口 -
ComVisible(true)
已设置在我们需要向COM公开的任何内容上
已在每个接口的所有成员上设置
DispId
我们的主要对象 Phone
是这样装饰的:
[ComSourceInterfaces(typeof(IClickToDialEvents))]
[ComDefaultInterface(typeof(IClickToDial))]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class Phone : IClickToDial
{
[...]
}
引用的接口 IClickToDial
和 IClickToDialEvents
的装饰方式如下:
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IClickToDial
{
[...]
}
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IClickToDialEvents
{
[...]
}
这些事件是 Phone
类的成员,下面是它们的样子:
[ComVisible(false)]
public delegate void OnCreateCtiConfHandler();
public event OnCreateCtiConfHandler OnCreateCtiConf;
进程事件循环如下所示:
private void processEventsInQueue()
{
while (true)
{
if (_eventQueue?.Count > 0)
{
var evnt = getNextEvent();
Trace.WriteLine($">>> Dequeued {evnt.Type}");
switch (evnt.Type)
{
case CTISoftphone.CTIEvent.OnCreateCtiConf:
if (OnCreateCtiConf != null)
OnCreateCtiConf();
else
addEvent(evnt);
break;
[...]
}
}
}
}
最后, addEvent
和 getNextEvent
方法如下所示:
private AvayaEvent getNextEvent()
{
lock (this)
return _eventQueue.Dequeue();
}
private void addEvent(AvayaEvent evnt)
{
lock (this)
{
try
{
_eventQueue.Enqueue(evnt);
}
catch (NullReferenceException)
{
//swallow the nullrefexception since we null out _eventQueue when we dont want to queue anything
}
}
}
最后,当我们用COM注册这个DLL时,我们使用以下RegAsm命令: .\RegAsm.exe 'C:\Program Files\Avaya\ClickToDial\<OurDll>.dll' /codebase /tlb
然后我们在VB6应用程序中引用生成的tlb文件 .
就像我说的,有时这很好,我们从AgentLogin方法返回没有问题 . Othertimes,我们无限期挂起,我总是看到这些挂起的访问冲突 . 我说分裂大约是50/50 .
这是我参与的第一个COM互操作项目,所以我可能错过了一些明显的东西,但这让我发疯了!如果需要更多信息/说明,请告诉我 .
在此先感谢您的帮助!