方法如何知道它是否在UI线程上运行? [英] How can a method know if it's running on the UI thread?

查看:185
本文介绍了方法如何知道它是否在UI线程上运行?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个简单的问题,但是我大约有80%的人确定该问题的答案将伴随着您做错了",因此我也要问一个不简单的问题.

I have a simple question, but I'm about 80% sure that the answer to the question will be accompanied by "you're doing it wrong," so I'm going to ask the not-simple question too.

一个简单的问题:我有一个公共类的公共方法.我希望它在UI线程上被调用时引发异常.我该怎么办?

The simple question: I have a public method of a public class. I want it to throw an exception if it's called on the UI thread. How can I do this?

一个简单得多的问题是:有没有更简单的方法来重构此设计?

The much-less-simple question is: is there an easier way to refactor this design?

背景:

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

I've developed a desktop program that interoperates with a legacy application via its API. The API is not remotely thread-safe. I've built a class library that encapsulates interoperation with the API (which involves marshalling and unmarshalling data in an enormous byte[] buffer and then calling an external function loaded from a DLL) so that as many implementation details of the legacy API are hidden from my code as possible. Because I knew that making multiple instances of the core API object would be a catastrophe, I implemented it as a static class.

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

I've also built a set of classes for running tasks in the background of my app. The TaskManager maintains a queue of Task objects and runs them using a BackgroundWorker. Using a background thread allows the desktop app's UI to remain responsive while interoperation with the turgid legacy app is going on; using a queue insures that only one task is calling the API at any given time.

不幸的是,我从没想过要在此设计中加入某些保护措施.我最近在代码中发现了一些我直接在UI线程上调用API的地方.我相信我已经修复了所有这些问题,但是我想保证我不会再这样做了.

Unfortunately, I never thought to build certain safeguards into this design. I've recently discovered places in the code where I was directly calling the API on the UI thread. I believe I've fixed all of them, but I'd like to guarantee I don't do this again.

如果从一开始就进行了适当的设计,那么我将使API包装器类成为非静态的,将其构造函数对除TaskManager之外的所有内容隐藏,然后将API类的实例传递给每个Task创建时.与API通话的Task调用的任何方法都需要传递给API对象.这将使得无法在前台线程上使用API​​.

If I'd designed this properly from the beginning, I'd have made the API wrapper class non-static, hidden its constructor from everything except the TaskManager, and then passed the instance of the API class to each Task when it gets created. Any method the Task called that talked to the API would need to be passed the API object. This would make it impossible to use the API on the foreground thread.

问题是,与API对话的代码有很多.实施此更改(我认为这最终是正确的选择)将触及所有方面.因此,与此同时,我想修改API的Call方法,以便在前台线程上调用它时会引发异常.

The thing is, there's a lot of code that talks to the API. Implementing this change (which I think is ultimately the right thing to do) will touch all of it. So in the meantime, I'd like to modify the API's Call method so that it throws an exception if it's being called on the foreground thread.

我知道我正在解决错误的问题.我能在骨头里感觉到它.但是我现在也很忙,看不到正确的解决方案.

I know I'm solving the wrong problem. I can feel it in my bones. But I'm also pretty wrapped up in it right now and can't quite see the right solution.

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

I clearly framed the question the wrong way, which is why it was hard to answer. I shouldn't be asking "How can this method know if it is running on the UI thread?" The real question is: "How can this method know if it is running on the wrong thread?" There could (in theory) be a thousand threads running. As JaredPar points out, there could be more than one UI thread. Only one thread is the right thread, and its thread ID is easy to find.

实际上,即使在我重构了该代码以便对其进行正确的设计(我今天主要是这样做的)之后,也值得在API中拥有一种机制来检查以确保它在适当的线程上运行. /p>

In fact, even after I refactor this code so that it's properly designed (which I mostly did today), it'll be worth having a mechanism in the API that checks to make sure it's being run on the appropriate thread.

推荐答案

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

Part of the problem with determining if you're on the UI thread or not is that there can be more than one UI thread. In WPF and WinForms it's quite possible to create more than one thread for displaying UI.

在这种情况下,听起来您的场景很受限制.最好的选择是在共享位置记录UI线程或后台线程的ID,然后使用Thread.CurrentThread.ManagedThreadId来确保您使用的线程正确.

In this case though, it sounds like you have a fairly constrained scenario. The best bet is to record the Id of the UI thread or background thread in a shared location and then use Thread.CurrentThread.ManagedThreadId to ensure you're on the correct thread.

public class ThreadUtil {
  public static int UIThreadId;

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

此方法有两个警告.您必须以原子方式设置UIThreadId,并且必须在运行任何后台代码之前进行设置.最好的方法是在程序启动时添加以下几行

This approach has a couple of caveats. You must set the UIThreadId in a atomic manner and must do so before any background code runs. The best way is to probably add the following lines to your program startup

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

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

Another trick is to look for a SynchronizationContext. Both WinForms and WPF will setup a SynchronizationContext on their UI threads in order to allow communication with background threads. For a background created and controlled by your program (i really want to stress that point) there will not be a SynchronizationContext installed unless you actually install one. So the following code can be used in that very limited circumstance

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

这篇关于方法如何知道它是否在UI线程上运行?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆