如何处理可能需要很长时间才能启动的启动/停止API接口

时间:2009-04-10 20:39:27

标签: c# .net multithreading dispose

我有一个应用程序,当呼叫路由到用户的扩展时,需要使用供应商提供的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线程中添加了另一层抽象,并且在这里没有解决问题。

目前,为了开发其余的代码,我完全消除了线程。但是,在我发布应用程序之前,我需要弄明白这一点。

任何人

谢谢,
戴夫

6 个答案:

答案 0 :(得分:1)

您可以进一步封装初始化逻辑并检查主初始化线程中的ManualResetEvent,同时生成工作线程以进行实际初始化。如果在初始化时从主线程设置了ManualResetEvent,则可以中止执行实际初始化的线程并进行清理,否则如果工作线程完成,则只需停止等待ManualResetEvent。 这有点肮脏和棘手,但应该做它应该做的事情;)

答案 1 :(得分:0)

首先,如果您需要关闭应用程序并且Initialize()仍在运行,则只有两个选项:

  1. 等待'Initialize'完成
  2. 中止正在运行'Initialize'的主题。
  3. 中止线程应该始终是最后一个选项,但是如果你真的需要停止应用程序并且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()返回之前关闭的情况的好主意。最终的代码将是这些解决方案的综合。

感谢所有人的反馈!

戴夫