我有一个应用程序,当呼叫路由到用户的扩展时,需要使用供应商提供的API来执行屏幕弹出。另一位开发人员致力于使API工作并提供了一个不太正确的原型包装类,我正在努力使其正确。
由于此API在返回之前可能会阻塞几分钟,因此开发人员在单独的线程中启动了API接口代码,如下所示:
// constructor
public ScreenPop( ...params... )
{
...
t = new Thread( StartInBackground );
t.Start();
)
private void StartInBackground()
{
_listener = new ScreenPopTelephoneListener();
_bAllocated = true;
_listener.Initialize( _address );
_bInitialized = true;
_listener.StartListening( _extension );
_bListening = true;
}
如果电话服务器没有响应,对Initialize的调用可能会挂起。
我不认为开发人员这样做是故意的,但是当API以这种方式初始化时,线程会继续运行,所以现在我必须在我清理之前停止线程我想要。我还必须使用Invoke从线程中获取事件,正如其他人在我之前的问题中所指出的那样,但现在已经有效了。
无论如何,这是用来关闭所有内容并清理的逻辑:
public void ShutdownFunc()
{
try
{
if ( _bListening )
{
_listener.StopListening();
_bListening = false;
}
if ( _bInitialized )
{
_listener.Shutdown();
_bInitialized = false;
}
if ( _bAllocated )
{
_listener = null;
_bAllocated = false;
}
}
}
当然,在整个混乱中有一个try / catch来防止未处理的异常。
我最大的问题是,当API接口代码在单独的线程中运行时,如何调用关闭代码?我需要清理以防用户注销然后重新登录,因为如果我不这样做,API就会混淆。 Thread.Abort不起作用,因为它不在捕获异常的地方,并且“volatile bool”方法也不起作用,原因很明显(大多数情况下,API线程不活动)。 / p>
因此,如果API接口代码在单独的线程上运行,似乎无法调用关闭逻辑。 Thread.Abort()绕过所有逻辑并杀死线程而不清理。从GUI线程调用关闭逻辑在调用Shutdown方法时会挂起。那么,我该怎么办?
IDispose接口只是在GUI线程中添加了另一层抽象,并且在这里没有解决问题。
目前,为了开发其余的代码,我完全消除了线程。但是,在我发布应用程序之前,我需要弄明白这一点。
任何人
谢谢,
戴夫
答案 0 :(得分:1)
您可以进一步封装初始化逻辑并检查主初始化线程中的ManualResetEvent,同时生成工作线程以进行实际初始化。如果在初始化时从主线程设置了ManualResetEvent,则可以中止执行实际初始化的线程并进行清理,否则如果工作线程完成,则只需停止等待ManualResetEvent。 这有点肮脏和棘手,但应该做它应该做的事情;)
答案 1 :(得分:0)
首先,如果您需要关闭应用程序并且Initialize
()仍在运行,则只有两个选项:
中止线程应该始终是最后一个选项,但是如果你真的需要停止应用程序并且Initialize
没有超时参数,那就别无选择了。
另外,我建议在您自己的类中包装第三方API,在其上实现IDisposable
并使用一些枚举而不是像ShutdownFunc
中那样单独的bool标记来保持API状态。在其中添加一些简单的自动化机器逻辑来处理每个状态。并将可能的状态保持在最低限度。
或者可能将该供应商的库扔进垃圾桶;)
答案 2 :(得分:0)
我重新设计了关闭功能的逻辑,以摆脱浮动的少数布尔值。这是新代码的样子:
private enum ApiState { Unallocated, Allocated, Initialized, Listening };
private ApiState _apiState = ApiState.Unallocated;
private void StartInBackground()
{
_listener = new ScreenPopTelephoneListener();
_apiState = ApiState.Allocated;
_phoneListener.Initialize( _strAddress );
_apiState = ApiState.Initialized;
_phoneListener.StartListening( _intExtension.ToString() );
_apiState = ApiState.Listening;
}
public void ShutdownFunc
{
try
{
if ( ApiState.Listening == _apiState )
{
_listener.StopListening();
_listener.Shutdown();
}
if ( ApiState.Initialized == _apiState )
{
_listener.Shutdown();
}
}
catch {}
finally
{
_listener = null;
_apiState = ApiState.Unallocated;
}
}
然而,问题仍然存在,我无法在不挂起GUI线程的情况下调用Shutdown逻辑。是否有任何方法可以使用来自GUI线程的调用来中断工作线程,而不是Abort?我是否可以设置事件处理程序并使用GUI线程中手动生成的事件来停止工作线程?有人试过吗?
我没有尝试过有关为初始化调用启动新线程的建议,因为这只能解决部分问题,而且一旦我解决了真正的问题就可能没有必要。
答案 3 :(得分:0)
好的,这是另一种尝试。我试图在线程上声明一个事件并引发该事件,从运行API的线程运行关闭逻辑。没有运气,它仍然试图在GUI线程上运行关闭逻辑并挂起应用程序。
以下是适用的代码:
public delegate void HiPathScreenPop_ShutdownEventHandler( object parent );
class HiPathScreenPop
{
// Shutdown event, raised to terminate API running in separate thread
public event HiPathScreenPop_ShutdownEventHandler Shutdown;
private Thread _th;
//ctor
public HiPathScreenPop()
{
...
_th = new Thread( StartInBackground );
_th.Start();
}
private void StartInBackground()
{
_phoneListener = new ScreenPopTelephoneListener();
_apiState = ApiState.Allocated;
_phoneListener.StatusChanged += new _IScreenPopTelephoneListenerEvents_StatusChangedEventHandler( StatusChangedEvent );
_phoneListener.ScreenPop += new _IScreenPopTelephoneListenerEvents_ScreenPopEventHandler( ScreenPopEvent );
this.Shutdown += new HiPathScreenPop_ShutdownEventHandler( HiPathScreenPop_Shutdown );
_phoneListener.Initialize( _strAddress );
_apiState = ApiState.Initialized;
_phoneListener.StartListening( _intExtension.ToString() );
_apiState = ApiState.Listening;
}
public void KillListener()
{
OnShutdown();
}
private void OnShutdown()
{
if ( this.Shutdown != null )
{
this.Shutdown( this );
}
}
void HiPathScreenPop_Shutdown( object parent )
{
// code from previous posts
}
}
当我打电话给KillListener时似乎发生了这样的事情:
1)在UI线程上调用OnShutdown()(OK)
2)关闭(this)被调用(这里是我希望在后台线程上引发事件的地方
3)在UI线程上调用HiPathScreenPop_Shutdown(),并挂起_phoneListener.ShutDown()调用,就像我没有使用事件机制一样。
好的,那么我错过了什么样的魔法,或完全是误会?似乎我创建的所有管道都没有做太多的事情,除了杂乱的代码......感谢您的任何回应, 戴夫
答案 4 :(得分:0)
当您直接从线程A调用事件处理程序时,该事件的所有处理程序也在线程A上执行。无法将该处理程序注入到其他线程中。你可以生成另一个线程来运行Shutdown,但这仍然无法解决Initialize尚未完成的问题 - 它只是再次移动问题。它允许您为用户显示等待对话框。
最终,如果这行代码暂停一段时间:
_phoneListener.Initialize( _strAddress );
然后除了等待完成之外没有什么可以做的(无论是在UI线程上还是在具有某种形式的等待UI的另一个线程中)并在该调用之后进行检查,导致处理程序线程退出。 / p>
_phoneListener.Initialize( _strAddress );
if (_exit.WaitOne(0))
{
return;
}
在主关闭例程中,您需要以下内容:
_exit.Set();
_th.Join();
// Rest of cleanup code
然后等待线程退出。我不建议在线程上调用Abort,因为你不知道线程引用的任何状态会发生什么。如果您必须中止一个线程,那么计划重新启动整个应用程序是安全的。
答案 5 :(得分:0)
在这里的开发人员的帮助下,我能够根据需要使代码正常工作。我需要做的是除了调用Initialize()之外,从线程中取出所有内容,以便API实例不在单独的线程上运行。然后我有一个不同的问题,因为状态是从2个不同的线程设置的,而不是我在清理时所期望的那样。
以下是最终解决方案:
// declare a background worker to init the API
private BackgroundWorker _bgWorker;
// ctor
public HiPathScreenPop( ... )
{
...
_phoneListener = new ScreenPopTelephoneListener();
_apiState = ApiState.Allocated;
_phoneListener.StatusChanged += new _IScreenPopTelephoneListenerEvents_StatusChangedEventHandler( StatusChangedEvent );
_phoneListener.ScreenPop += new _IScreenPopTelephoneListenerEvents_ScreenPopEventHandler( ScreenPopEvent );
_bgWorker = new BackgroundWorker();
_bgWorker.DoWork += new DoWorkEventHandler(StartInBackground);
_bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler( bgWorker_RunWorkerCompleted );
_bgWorker.RunWorkerAsync();
}
void _bgWorker_DoWork( object sender, DoWorkEventArgs e )
{
_phoneListener.Initialize( _strAddress );
}
void bgWorker_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
{
_apiState = ApiState.Initialized; // probably not necessary...
_phoneListener.StartListening( _intExtension.ToString() );
_apiState = ApiState.Listening;
}
现在,API实例在与UI相同的线程中运行,每个人都很高兴。对Shutdwn()的调用不再挂起,我不需要Invoke()用于回调,并且长时间运行的代码在后台工作线程中执行。
此代码未显示的是前面介绍的有关如何处理用户在调用Initialize()返回之前关闭的情况的好主意。最终的代码将是这些解决方案的综合。
感谢所有人的反馈!
戴夫