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

2023-11-27

我正在玩Task.ConfigureAwait为了更好地了解引擎盖之外的情况。所以我在将一些 UI 访问内容与ConfigureAwait.

下面是使用简单 Windows 窗体的示例应用程序,其中 1Button接下来是测试结果:

private async void btnDoWork_Click(object sender, EventArgs e)
{
    List<int> 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访问(跨线程 操作)让循环的下一个迭代在 Main 上执行 线 ?

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

  3. 为什么在没有附加调试器的情况下执行上述示例(直接从exe) 没有表现出相同的行为?


这让我有点摸不着头脑,但终于找到了窍门。

setter 的代码Button.Text属性是:

  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;
      }
    }
  }

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

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

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

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

if (value == this.Text)
  return;

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

UI跨线程操作异常后的Task.ConfigureAwait行为 的相关文章

随机推荐