我可以从后台线程更新UI,为什么?

5

每个人都知道,不允许在后台线程中更新UI(或者不允许吗?)

我进行了一些实验。这里是一段代码:

var thread = new Thread(() => progressBar1.Increment(50));
            thread.IsBackground = true;
            thread.Start();
            thread.Join();

我把这段代码放在某个按钮点击事件的处理程序中。你知道吗?我的进度条正在增加......来自后台线程。现在我很困惑。我不明白这是如何可能的,也不知道我做错了什么。

3
你误解了这个想法。与UI元素无关,而是与在UI线程中创建的内容有关。例如,如果你通过设计视图将progressBar1放在主窗体中,那么这段代码会触发一个错误。 - varocarbas
1
但是根据这个链接 - https://msdn.microsoft.com/en-us/library/ms171728%28v=vs.110%29.aspx 除了创建控件的线程之外,从其他线程访问控件元素是不允许的。 - Toddams
3个回答

7
我可以从后台线程更新UI,为什么?
很可能您只能在调试器外运行时这样做。这是因为该保护受Control.CheckForIllegalCrossThreadCalls属性控制。让我们看一下参考源代码
private static bool checkForIllegalCrossThreadCalls = Debugger.IsAttached;
public static bool CheckForIllegalCrossThreadCalls {
    get { return checkForIllegalCrossThreadCalls; }
    set { checkForIllegalCrossThreadCalls = value; }
}

正如您所见,此保护默认仅在调试时启用。
如果您在Main方法中添加以下行(在Application.Run之前),则可以启用此保护。
Control.CheckForIllegalCrossThreadCalls = true;

你的代码将不再起作用。


1
请注意,即使将 CheckForIllegalCrossThreadCalls 设置为 false,这样的调用仍然是错误/有缺陷的。该设置可以使此类错误更容易被发现和修复。 - Brian

5
每个人都知道,不允许从后台线程更新UI。
并非所有人都知道这一点。但事实如此。大多数情况下,从非UI线程更新UI元素是错误的。
我不明白怎么可能做到这一点。
你把一堆沙子倒进汽车的油底壳里。你不应该这样做。那么你为什么会这样做呢?这似乎是一个奇怪的问题,不是吗?
车有必要阻止你这样做吗?不是。发动机必须在你这样做时停止运行吗?也不是。发动机最终会损坏吗?可能。但不一定。
设计你的车的工程师们认为你不会犯这么错误,所以他们没有构建任何安全系统来防止或检测此问题。
我做错了什么?
你正在从后台线程更新UI。不要这样做。但是运行时不需要提供预防或检测您错误的安全系统。当您首先不应该做某事时,它不需要产生错误。它可以选择这样做,也可以选择不这样做。
或者,换句话说。假设您正在实现运行时,使其具有您认为具有的特性:即运行时将始终检测到错误的跨线程调用并在发生时产生错误。那是一种特征。必须有人设计,编写,测试和维护该代码。代码是什么样子的,以及与之相关的所有成本是多少?

很好的解释。我以为后台线程只是不会生效更改,现在一切都变得清晰了,谢谢。 - Toddams
在非 UI 线程中更新大多数 UI 元素是错误的。只是出于好奇,是否有任何可以从非 UI 线程更新的 UI 元素? - Luca Cremonesi
2
@LucaCremonesi:这里使用了“most”这个模棱两可的词语,以防止一些学究式的SO用户评论“不,由Frobozz公司在他们的Fribble Controls 2.5.6中制作的FrobButton小部件可以从任何线程中调用而不会出问题”。一个普遍陈述只需要一个反例就能被推翻;而“most”则需要50%的情况是反例才能被推翻。 - Eric Lippert

-1

就像varocarbas所说,您放入设计器中的任何组件都会引发跨线程异常。

要在线程之间进行访问,您需要使用invke、beginInvoke。

尝试运行

BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += (s1,e1) => 
{
    textBoxt1.Text = "foo";  // Exception here
} 
bw.RunWorkerCompleted += (1s, e1) =>
{
    textBoxt1.Text = "foo";  // No exception here off UI thread
}
bw.RunWorkerAsync();

替换为

bw.DoWork += (s1,e1) => 
{
    this.Invoke((MethodInvoker) delegate 
   {
        textBoxt1.Text = message;
   });  // No Exception now
} 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接