一个方法如何知道它是否在UI线程上运行?

时间:2009-05-02 23:48:08

标签: c# .net multithreading

我有一个简单的问题,但我大约80%肯定问题的答案会伴随着“你做错了”,所以我也会问这个不简单的问题。

简单的问题:我有一个公共类的公共方法。如果在UI线程上调用它,我希望它抛出一个异常。我怎么能这样做?

这个简单易懂的问题是:有没有更简单的方法来重构这个设计?

背景:

我开发了一个桌面程序,可以通过其API与遗留应用程序进行互操作。 API不是远程线程安全的。我已经构建了一个类库,它封装了与API的互操作(它涉及在巨大的byte []缓冲区中编组和解组数据,然后调用从DLL加载的外部函数),以便隐藏遗留API的许多实现细节从我的代码尽可能。因为我知道制作核心API对象的多个实例将是一场灾难,所以我将其实现为静态类。

我还在应用程序的后台构建了一组用于运行任务的类。 TaskManager维护Task个对象的队列,并使用BackgroundWorker运行它们。使用后台线程允许桌面应用程序的UI保持响应,同时与turgid遗留应用程序进行互操作;使用队列可确保在任何给定时间只有一个任务正在调用API。

不幸的是,我从没想过要在这个设计中建立某些保护措施。我最近在代码中发现了我在UI线程上直接调用API的位置。我相信我已经修好了所有这些,但我想保证我不再这样做了。

如果我从一开始就正确设计了这个,我就已经使API包装类非静态,从TaskManager以外的所有内容隐藏其构造函数,然后将API类的实例传递给每个Task创建时。调用API的Task调用的任何方法都需要传递给API对象。这将使得无法在前台线程上使用API​​。

问题是,有一个很多代码与API对话。实现这一改变(我认为最终是正确的做法)将触及所有这一切。所以在此期间,我想修改API的Call方法,以便在前台线程上调用它时抛出异常。

我知道我正在解决错误的问题。我可以在骨头里感受到它。但我现在也非常喜欢它,并且看不到正确的解决方案。

修改

我清楚地以错误的方式提出这个问题,这就是为什么很难回答的原因。我不应该问“这个方法如何知道它是否在UI线程上运行?”真正的问题是:“这个方法如何知道它是否在错误的线程上运行?” (理论上)可以运行一千个线程。正如JaredPar指出的那样,可能有多个UI线程。只有一个线程是正确的线程,其线程ID很容易找到。

事实上,即使我重构了这段代码以便它设计得恰当(我今天大部分时间都这样做了),但在API中检查以确保它在相应的线程上运行是值得的。 / p>

3 个答案:

答案 0 :(得分:3)

确定您是否在UI线程上的部分问题是可能存在多个UI线程。在WPF和WinForms中,很有可能创建多个用于显示UI的线程。

在这种情况下,听起来你有一个相当有限的场景。最好的办法是在共享位置记录UI线程或后台线程的Id,然后使用Thread.CurrentThread.ManagedThreadId确保您使用正确的线程。

public class ThreadUtil {
  public static int UIThreadId;

  public static void EnsureNotUIThread() {
    if ( Thread.CurrentThread.ManagedThreadId == UIThreadId ) {
      throw new InvalidOperationException("Bad thread");
    }
  }
}

这种方法有几点需要注意。您必须以原子方式设置UIThreadId,并且必须在运行任何后台代码之前执行此操作。最好的方法是将以下行添加到程序启动

Interlocked.Exchange(ref ThreadUtil.UIThreadID, Thread.CurrentThread.ManagedThreadId);

另一个技巧是寻找SynchronizationContext。 WinForms和WPF都会在其UI线程上设置SynchronizationContext,以允许与后台线程进行通信。对于由程序创建和控制的背景(我真的想强调这一点),除非您实际安装了SynchronizationContext,否则不会安装SynchronizationContext。因此,以下代码可以在非常有限的情况下使用

public static bool IsBackground() { 
  return null == SynchronizationContext.Current;
}

答案 1 :(得分:1)

我将反转ISynchronizeInvoke用例,并在ISynchronizeinvoke.InvokeRequired == false时抛出异常。这让WinForms可以处理寻找UI线程的大量工作。性能会有点糟糕,但这是一个调试场景 - 所以它真的没关系。您甚至可以隐藏#IF DEBUG标志后面的检查,以仅检查调试版本。

您需要为API提供对ISynchronizeInvoke的引用 - 但您可以在启动时轻松执行此操作(只需传递主窗体),或者让它使用静态Form.ActiveForm调用。

答案 2 :(得分:0)

Justin Rogers详细讨论了Invoke / BeginInvoke如何做到这一点,以及它变得难看的地方。

基本上,它回退到在UI线程上找到一些窗口,并调用GetWindowThreadProcessId来将当前线程id与窗口的ID进行比较。 (当然,当你只有一个UI线程时,你可以缓存线程ID)。