2

我有一个应用程序需要使用供应商提供的 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 线程不活动)。

因此,如果 API 接口代码在单独的线程上运行,似乎没有办法调用关闭逻辑。Thread.Abort() 绕过所有逻辑并在不清理的情况下杀死线程。调用 Shutdown 方法时,从 GUI 线程调用关闭逻辑会挂起。那么,我能做些什么呢?

IDispose 接口只是在 GUI 线程中增加了另一层抽象,并没有解决这里的问题。

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

有人吗?

谢谢,
戴夫

4

6 回答 6

1

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

于 2009-04-10T20:54:29.867 回答
0

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

  1. 等待“初始化”完成
  2. 中止正在运行“初始化”的线程。

中止线程应该始终是最后一个选项,但如果您确实需要停止应用程序并且Initialize没有超时参数,则没有其他方法。

另外,我建议将第 3 方 API 包装在您自己的类中,IDisposable在其上实现并使用一些枚举而不是像您在ShutdownFunc. 在其中添加一些简单的自动化机器逻辑来处理每个状态。并将可能的状态计数保持在所需的最小值。

或者也许把那个供应商的图书馆扔进垃圾桶;)

于 2009-04-10T21:05:37.017 回答
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 线程中手动生成的事件来停止工作线程吗?有没有人试过这个?

我没有尝试过有关为初始化调用启动新线程的建议,因为这只能解决部分问题,一旦我解决了真正的问题,可能就没有必要了。

于 2009-04-14T17:06:04.577 回答
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) OnShutdown() 在 UI 线程上被调用(OK)

2) Shutdown( this ) 被调用(这是我希望在后台线程上引发事件的地方

3) HiPathScreenPop_Shutdown() 在 UI 线程上被调用,并在 _phoneListener.ShutDown() 调用上挂起,就像我没有使用事件机制一样。

好的,那么我错过了什么魔法,或者完全误解了?似乎我创建的所有管道都没有做任何事情,只是弄乱了代码......

感谢您的任何回复,戴夫

于 2009-05-01T16:42:20.250 回答
0

当您直接从线程 A 调用事件处理程序时,该事件的所有处理程序也会在线程 A 上执行。无法将该处理程序注入到您的其他线程中。您可以生成另一个线程来运行 Shutdown,但这仍然不能解决 Initialize 尚未完成的问题 - 它只是再次解决了问题。它确实允许您为用户显示一个等待对话框。

最终如果这行代码挂了一段时间:

_phoneListener.Initialize( _strAddress );

那么除了等待它完成(在UI线程上或在具有某种等待UI的另一个线程中)并在该调用后进行检查以导致处理程序线程退出之外,您实际上无能为力。

_phoneListener.Initialize( _strAddress );
if (_exit.WaitOne(0))
{
    return;
}

在您的主要关闭例程中,您需要以下内容:

_exit.Set();
_th.Join();

// Rest of cleanup code

然后这将等待线程退出。我不建议在线程上调用 Abort,因为您不知道线程引用的任何状态会发生什么。如果您必须中止线程,则计划重新启动整个应用程序以确保安全。

于 2009-05-01T17:18:40.927 回答
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() 返回之前关闭的情况的好主意。最终的代码将是这些解决方案的综合。

感谢大家的好评!

戴夫

于 2009-05-01T21:39:14.903 回答