我不问一个问题,而是讨论C#编译器中的错误/错误。
// do a heavy job it take 2s and return "1"
public async Task<string> DoJob() {
var task = new Task<string>(() => {
Thread.Sleep(2000);
return "1";
});
task.Start();
return await task;
}
private async void button1_Click(object sender, EventArgs e) {
var task= DoJob();
textBox1.Text += await task;
this.Text += "2";
}
当我点击button1 3次时,我期待:
textBox1.Text == "111"
this.Text == "222"
但结果是:
textBox1.Text == "1"
this.Text == "222"
另一个错误,在等待2s(2s之前)时,我通过输入键盘更改textBox1.Text,但结果仍为“1”,而不是附加到文本末尾(+ =运算符)。
根据我的知识异步和等待关键字什么都不做,但帮助编译器知道将代码放入块的位置(纠正我):
例
输入:
private async void button1_Click(object sender, EventArgs e) {
var task= DoJob();
textBox1.Text += await task;
this.Text += "2";
}
输出:这给出了我期望的结果但与C#Compiler不同。这也没有上面的另一个错误。
private void button1_Click(object sender, EventArgs e) {
var task= DoJob();
task.ContinueWith((_task) => {
this.Invoke(new Action(() => {
textBox1.Text += _task.Result;
this.Text += "2";
}));
});
}
但MS C#Compiler做了类似的事情:
private void button1_Click(object sender, EventArgs e) {
var task = DoJob();
var left = textBox1.Text;
task.ContinueWith((_task) => { // textBox1.Text += await task;
this.Invoke(new Action(() => { //
textBox1.Text = left + _task.Result; //
this.Text += "2"; // this.Text += "2";
}));
});
}
你认为这是bug还是微软故意这样做?
某些语言(如C ++)允许编译器选择在给定语句中计算的顺序表达式。但C#消除了这种模糊性:评估总是从左到右。
textBox1.Text += await task;
在C#中,没有特殊的+ =运算符,它只是扩展为:
textBox1.Text = textBox1.Text + await task;
现在,如果我们从左到右进行评估,它应该评估的第一件事是textBox1.set_Text用于赋值。如果你要在右侧做一些会改变textBox1值的东西,你仍然会分配给原始的textBox1,而不是新的值。
接下来要评估的是textBox1.get_Text。这应该返回你的值“”。
然后它评估“等待任务”。此时,它等待并且无法继续评估语句,因此它将当前堆栈帧的重要内容转储到堆对象中供以后使用。当它回来继续评估时,我们现在评估了以下信息:
剩下要做的就是将捕获的“”添加到等待的“1”并将其分配给捕获的textBox1。
如果您快速连续多次执行此操作,则每次所有3个输入都是相同的。换句话说,它的行为与标准所描述的完全一致。人们给出的建议是将它放在上一行,如下所示:
var temp = await task;
textBox1.Text += temp;
这只是在textBox1.Text之前移动await。您可以实际重新排序内联并提供预期结果,只需通过反转语句中的表达式顺序:
textBox1.Text = string.Concat(
new[] { await task, textBox1.Text }.Reverse());
考虑以下两条路径:
textBox1.Text += await task;
与
string newText = await task;
textBox1.Text += newText;
在第一个示例中,textBox1.Text的当前值首先通过所有三次单击立即读取,这可能只是原始的空文本框值。然后,当每个完成时,它会写入原始值(就像读取时一样,提醒仍然是空字符串)加上要连接的“1”。因此,如果您快速单击该按钮,则所有三个线程都会写入“”+“1”的值
在第二个示例中,您等待获取要连接的结果文本,然后(在UI线程上)执行+ =操作,该操作将不间断地读取,连接和写入。
如果等待+ =,则在开始等待之前读取原始值,并将结果串联应用于保存在await上下文中的值。
这有意义吗?
这里没有编译器错误。但是,这一行是你的问题
textBox1.Text += await task;
如果您更改了以下内容,您会发现一致的结果。
var result = await task;
textBox1.Text += result;
一个简单的比喻是你要求某人得到一个结果(例如一个单词或数字),当你完成自己的工作后,你想要将结果添加到他们自己的工作中。当你几次非常快地完成这个问题时就会出现问题。你得到了他们原来的价值3次(你说的是,给我你所拥有的,给我你所拥有的东西,给我你所拥有的东西),你要做你的3个单独的作品,当你回来时你只需添加它到原始值(未修改的值),这似乎是有效地覆盖了值而不起作用。