为了账号安全,请及时绑定邮箱和手机立即绑定

C# 编译器不正确的异步,等待?

C# 编译器不正确的异步,等待?

C#
jeck猫 2022-01-09 15:13:43
我不是在问问题,而是在讨论 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"还有另一个错误,在等待 2 秒(在 2 秒之前)时,我通过输入键盘更改了 textBox1.Text,但结果仍然相同“1”,而不是附加到文本末尾(+= 运算符)。根据我的知识 async 和 await 是关键字什么都不做,只是帮助编译器知道将代码放入块的位置(纠正我):例子输入:private async void button1_Click(object sender, EventArgs e) {    var task= DoJob();    textBox1.Text += await task;    this.Text += "2";}输出:这给出了我期望的结果,但与 C# 编译器不同。而且这也没有上面还有一个bug。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# 编译器会做这样的事情: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";        }));    });}您认为这是错误还是微软故意这样做?
查看完整描述

3 回答

?
阿波罗的战车

TA贡献1862条经验 获得超6个赞

考虑以下两条路径:


textBox1.Text += await task;

对比


string newText = await task;

textBox1.Text += newText;

在第一个示例中,textBox1.Text 的当前值首先被所有三次单击立即读取,这可能只是原始的空文本框值。然后,当每个完成时,它会写入原始值(就像读取时一样,提醒仍然是空字符串)加上您要连接的“1”。所以如果你快速点击按钮,三个线程都会写入“”+“1”的值


在第二个示例中,您等待获取要连接的结果文本,然后(在 UI 线程上)执行 += 操作,该操作将不间断地读取、连接和写入。


如果您在 += 中等待,则在开始等待之前读取原始值,并将结果连接应用于保存在等待上下文中的值。


现在这有意义吗?


查看完整回答
反对 回复 2022-01-09
?
慕仙森

TA贡献1827条经验 获得超7个赞

某些语言(例如 C++)允许编译器选择在给定语句中计算的顺序表达式。但是 C# 消除了这种歧义:评估总是从左到右。

textBox1.Text += await task;

在 C# 中,没有特殊的 += 运算符,它只是扩展为:

textBox1.Text = textBox1.Text + await task;

现在,如果我们从左到右评估,它应该评估的第一件事是 textBox1.set_Text 用于赋值。如果您要在右侧执行一些会更改 textBox1 的值的操作,您最终仍会分配给原始 textBox1,而不是新值。

接下来要评估的是 textBox1.get_Text。这应该返回您的“”值。

然后它评估“等待任务”。此时,它陷入等待,无法继续评估语句,因此它将当前堆栈帧的重要内容转储到堆对象中以备后用。当它回来继续评估时,我们现在评估了以下信息:

  1. 我们要写入的对象的地址。

  2. 字符串值“”。

  3. 字符串值“1”。

剩下要做的就是将捕获的“”添加到等待的“1”,并将其分配给捕获的 textBox1。

如果您快速连续多次执行此操作,则所有 3 个输入每次都将相同。换句话说,它的行为与标准所描述的完全一致。人们给出的建议将其放在上一行,如下所示:

var temp = await task;
textBox1.Text += temp;

这只是将等待移到 textBox1.Text 之前。您实际上可以进行重新排序内联并给出预期的结果,只需颠倒语句中表达式的顺序即可:

textBox1.Text = string.Concat(
    new[] { await task, textBox1.Text }.Reverse());


查看完整回答
反对 回复 2022-01-09
?
呼唤远方

TA贡献1856条经验 获得超11个赞

这里没有编译器错误。但是,这条线是你的问题

textBox1.Text += await task;

如果您更改以下内容,您会发现一致的结果。

var result = await task;
textBox1.Text += result;

一个简单的类比是您向某人询问结果(例如单词或数字),当您完成自己的工作后,您希望将结果添加到他们的结果中。当您非常快地多次执行此操作时,就会出现问题。你得到了他们原来的价值 3 次(你说,给我你所拥有的,给我你所拥有的,给我你所拥有的),你要去做你的 3 个独立的工作,当你回来时你只需添加它到原始值(未修改的值),这似乎有效地覆盖了该值并且不起作用。


查看完整回答
反对 回复 2022-01-09
  • 3 回答
  • 0 关注
  • 135 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信