Zei*_*kki 6 c# multithreading asynchronous winforms
我玩这个游戏是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++;
}
}
Run Code Online (Sandbox Code Playgroud)
现在点击按钮后:
测试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
Run Code Online (Sandbox Code Playgroud)
测试 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
Run Code Online (Sandbox Code Playgroud)
测试 3 日志结果:( 与 1 相同,但没有调试器)
1. Control Text Changed
Run Code Online (Sandbox Code Playgroud)
所以问题是:
为什么第一个 UI 访问(跨线程操作)的下一次循环迭代在主线程上执行?
为什么手动异常不会导致相同的行为?
为什么在没有附加调试器(直接从exe)的情况下执行上述示例不会显示相同的行为?
这让我有点摸不着头脑,但终于找到了窍门。
属性设置器的代码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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
抛出异常的行是this.WindowText = value;(因为它在内部尝试访问Handle按钮的属性)。诀窍在于,就在之前,它text在某种缓存中设置了属性:
if (this.CacheTextInternal)
this.text = value;
Run Code Online (Sandbox Code Playgroud)
老实说,我不知道这个缓存是如何工作的,或者它何时被激活(事实证明,它似乎在这种情况下被激活)。但正因为如此,即使抛出异常,文本也会被设置。
在循环的进一步迭代中,不会发生任何事情,因为该属性进行了特殊检查以确保您不会两次设置相同的文本:
if (value == this.Text)
return;
Run Code Online (Sandbox Code Playgroud)
如果您更改循环以每次设置不同的文本,那么您将看到每次迭代时都会抛出一致的异常。