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

我不是在问问题,而是在讨论 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";

        }));

    });


}

您认为这是错误还是微软故意这样做?


jeck猫
浏览 136回答 3
3回答

阿波罗的战车

考虑以下两条路径:textBox1.Text += await task;对比string newText = await task;textBox1.Text += newText;在第一个示例中,textBox1.Text 的当前值首先被所有三次单击立即读取,这可能只是原始的空文本框值。然后,当每个完成时,它会写入原始值(就像读取时一样,提醒仍然是空字符串)加上您要连接的“1”。所以如果你快速点击按钮,三个线程都会写入“”+“1”的值在第二个示例中,您等待获取要连接的结果文本,然后(在 UI 线程上)执行 += 操作,该操作将不间断地读取、连接和写入。如果您在 += 中等待,则在开始等待之前读取原始值,并将结果连接应用于保存在等待上下文中的值。现在这有意义吗?

慕仙森

某些语言(例如 C++)允许编译器选择在给定语句中计算的顺序表达式。但是 C# 消除了这种歧义:评估总是从左到右。textBox1.Text&nbsp;+=&nbsp;await&nbsp;task;在 C# 中,没有特殊的 += 运算符,它只是扩展为:textBox1.Text&nbsp;=&nbsp;textBox1.Text&nbsp;+&nbsp;await&nbsp;task;现在,如果我们从左到右评估,它应该评估的第一件事是 textBox1.set_Text 用于赋值。如果您要在右侧执行一些会更改 textBox1 的值的操作,您最终仍会分配给原始 textBox1,而不是新值。接下来要评估的是 textBox1.get_Text。这应该返回您的“”值。然后它评估“等待任务”。此时,它陷入等待,无法继续评估语句,因此它将当前堆栈帧的重要内容转储到堆对象中以备后用。当它回来继续评估时,我们现在评估了以下信息:我们要写入的对象的地址。字符串值“”。字符串值“1”。剩下要做的就是将捕获的“”添加到等待的“1”,并将其分配给捕获的 textBox1。如果您快速连续多次执行此操作,则所有 3 个输入每次都将相同。换句话说,它的行为与标准所描述的完全一致。人们给出的建议将其放在上一行,如下所示:var&nbsp;temp&nbsp;=&nbsp;await&nbsp;task; textBox1.Text&nbsp;+=&nbsp;temp;这只是将等待移到 textBox1.Text 之前。您实际上可以进行重新排序内联并给出预期的结果,只需颠倒语句中表达式的顺序即可:textBox1.Text&nbsp;=&nbsp;string.Concat( &nbsp;&nbsp;&nbsp;&nbsp;new[]&nbsp;{&nbsp;await&nbsp;task,&nbsp;textBox1.Text&nbsp;}.Reverse());

呼唤远方

这里没有编译器错误。但是,这条线是你的问题textBox1.Text&nbsp;+=&nbsp;await&nbsp;task;如果您更改以下内容,您会发现一致的结果。var&nbsp;result&nbsp;=&nbsp;await&nbsp;task; textBox1.Text&nbsp;+=&nbsp;result;一个简单的类比是您向某人询问结果(例如单词或数字),当您完成自己的工作后,您希望将结果添加到他们的结果中。当您非常快地多次执行此操作时,就会出现问题。你得到了他们原来的价值 3 次(你说,给我你所拥有的,给我你所拥有的,给我你所拥有的),你要去做你的 3 个独立的工作,当你回来时你只需添加它到原始值(未修改的值),这似乎有效地覆盖了该值并且不起作用。
打开App,查看更多内容
随时随地看视频慕课网APP