UI.跨线程操作exception后的Task.ConfigureAwait行为

我正在玩Task.ConfigureAwait ,以便更好地了解什么是超越引擎盖。 所以我在将一些UI访问内容与ConfigureAwait结合起来时遇到了这种奇怪的行为。

下面是使用简单窗体的示例应用程序,其中包含1个Button后跟测试结果:

 private async void btnDoWork_Click(object sender, EventArgs e) { List Results = await SomeLongRunningMethodAsync().ConfigureAwait(false); int retry = 0; while(retry < RETRY_COUNT) { try { // commented on test #1 & #3 and not in test #2 //if(retry == 0) //throw new InvalidOperationException("Manually thrown Exception"); btnDoWork.Text = "Async Work Done"; Logger.Log("Control Text Changed", logDestination); return; } catch(InvalidOperationException ex) { Logger.Log(ex.Message, logDestination); } retry++; } } 

现在按钮后点击:

测试1日志结果:(正如上面的代码)

 1. Cross-thread operation not valid: Control 'btnDoWork' accessed from a thread other than the thread it was created on. 2. Control Text Changed 

测试2日志结果:(手动例外抛出未注释)

 1. Manually thrown Exception 2. Cross-thread operation not valid: Control 'btnDoWork' accessed from a thread other than the thread it was created on. 3. Control Text Changed 

测试3日志结果:(与1相同,但没有调试器)

 1. Control Text Changed 

所以问题是:

  1. 为什么第一个UI访问(跨线程操作)在主线程上执行循环的下一次迭代?

  2. 为什么手动exception不会导致相同的行为?

  3. 为什么没有附加调试器(直接从exe )执行上面的示例不会显示相同的行为?

这个让我挠了一下头,但终于找到了诀窍。

Button.Text属性的setter代码是:

  set { if (value == null) value = ""; if (value == this.Text) return; if (this.CacheTextInternal) this.text = value; this.WindowText = value; this.OnTextChanged(EventArgs.Empty); if (!this.IsMnemonicsListenerAxSourced) return; for (Control control = this; control != null; control = control.ParentInternal) { Control.ActiveXImpl activeXimpl = (Control.ActiveXImpl) control.Properties.GetObject(Control.PropActiveXImpl); if (activeXimpl != null) { activeXimpl.UpdateAccelTable(); break; } } } 

抛出exception的行是this.WindowText = value; (因为它在内部尝试访问按钮的Handle属性)。 诀窍是,就在之前,它在某种缓存中设置text属性:

 if (this.CacheTextInternal) this.text = value; 

老实说,我不知道这个缓存是如何工作的,或者它是否被激活(事实certificate,它似乎在这种精确的情况下被激活)。 但正因为如此,即使抛出exception,也会设置文本。

在循环的进一步迭代中,没有任何反应,因为该属性有一个特殊的检查,以确保您没有设置两次相同的文本:

 if (value == this.Text) return; 

如果您每次都更改循环以设置不同的文本,那么您将看到在每次迭代时都会一致地抛出exception。