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

问题描述 投票:-1回答:3

我不问一个问题,而是讨论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# asynchronous async-await
3个回答
3
投票

某些语言(如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之前移动await。您可以实际重新排序内联并提供预期结果,只需通过反转语句中的表达式顺序:

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

4
投票

考虑以下两条路径:

textBox1.Text += await task;

string newText = await task;
textBox1.Text += newText;

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

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

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

这有意义吗?


2
投票

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

textBox1.Text += await task;

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

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

一个简单的比喻是你要求某人得到一个结果(例如一个单词或数字),当你完成自己的工作后,你想要将结果添加到他们自己的工作中。当你几次非常快地完成这个问题时就会出现问题。你得到了他们原来的价值3次(你说的是,给我你所拥有的,给我你所拥有的东西,给我你所拥有的东西),你要做你的3个单独的作品,当你回来时你只需添加它到原始值(未修改的值),这似乎是有效地覆盖了值而不起作用。

© www.soinside.com 2019 - 2024. All rights reserved.