【问题标题】:Value is not updating after async method resume异步方法恢复后值未更新
【发布时间】:2015-12-10 02:25:03
【问题描述】:

看这段代码:

public class SharedData
{
    public int Value { get; set; }
}

void button1_Click(object sender, EventArgs e)
{
    AAA();
}

async Task BBB(SharedData data)
{
    await Task.Delay(TimeSpan.FromSeconds(1));
    MessageBox.Show(data.Value.ToString()); //<---- I always see 0 here,
    data.Value = data.Value + 1;
}

async Task<int> AAA()
{
    SharedData data = new SharedData();
    var task1 = BBB(data);
    var task2 = BBB(data);
    var task3 = BBB(data);

    await Task.WhenAll(task1, task2, task3);
    MessageBox.Show(data.Value.ToString());  //<--- this does show 3
    return data.Value;
}

这是一个 GUI(Windows 窗体)应用程序,这意味着只有一个线程执行每一行代码。

所有BBB(data) 调用都非常快速地调用,无需等待。 每个 BBB 调用都进入 BBB method ,看到 await 没有完成并返回(到 AAA)。

现在,当一秒钟(大约)过去时,所有的继续都发生在 GUI 线程中

问题

Continuations 不会同时发生,因为它是一个 GUI 线程。 所以之前的声明:

data.Value = data.Value + 1;

一定发生过。

换句话说,

我知道所有 BBBs 都使用相同的初始值 data 调用,但延续不会同时发生

GUI 线程必须最终运行:

继续#1

 MessageBox.Show(data.Value.ToString()); 
 data.Value = data.Value + 1; //So this basically should do 0-->1
....

继续#2

 MessageBox.Show(data.Value.ToString()); // Why data.Value still "0" ??
 data.Value = data.Value + 1;  
....

继续#3

 MessageBox.Show(data.Value.ToString()); // Why data.Value still "0" ??
 data.Value = data.Value + 1;

看起来延续计划不是作为一个整体而是作为共享量子?

【问题讨论】:

  • @Eser 我不明白,每个延续都有自己的 gui 线程。这里没有个其他线程。为什么要锁?这不是(!)在线程池线程中运行,这意味着没有要保留的上下文(并且每个延续都可以在另一个线程池线程中运行)..
  • 更改BBB 以接受并使用延迟值:async Task BBB(SharedData data, int delay),然后传递不同的值而不是相同的1。想知道为什么现在您一次看到三个消息框,而不是一次看到一个? :)
  • @Noseratio 是的,我很想知道 :-) 你能解释一下吗? This code 一起显示 3 个 MSgBox,而不是一次显示 1 个(如我的示例中所示)。你能解释一下为什么吗?
  • 将此归咎于底层 Win32 MessageBox API。如果您使用Form.ShowDialog,您会看到所有 3 个框都按照正确的 1-2-3 顺序排列。使用MessageBox,其他两个框仍然存在,只是不可见。他们确实已经开始了他们的嵌套模式消息循环,一个又一个,因为await Task.Delay(1000) 延续仍然被抽。嵌套消息循环开始,由赢得比赛的任何延续引起,因此显示窗口的代码在该消息循环终止之前不会启动。
  • Royi,@StephenCleary 的answer 比我上面的评论更详细地解释了它。

标签: c# .net winforms async-await


【解决方案1】:

发生这种情况是因为您使用 MessageBox.Show 进行调试打印并显示模式消息框会阻止代码流但不会阻止 UI 线程(否则您无法与消息框交互)。

因此,当通过显示消息框“阻止”第一个延续时,下一个延续可以运行,然后是第三个。通过显示消息框,它们都被“阻止”了,但 UI 线程本身并没有。

这就是为什么它们都显示0 并且只有当您释放消息框时它们才能继续运行并增加变量。

如果在显示消息框之前使用Console.WriteLine 打印值,您可以看到:

async Task BBB(SharedData data)
{
    await Task.Delay(TimeSpan.FromSeconds(1));
    Console.WriteLine(data.Value);
    MessageBox.Show(data.Value.ToString());
    data.Value = data.Value + 1;
}

您会注意到 0 在 1 秒后被所有延续打印 3 次,而不是在您关闭消息框之后。

基本上,延续并发运行,但不是使用同一个线程并行运行,因为它们中包含MessageBox.Show

如果您使用Console.WriteLine 而不是MessageBox.Show,您将看到该值一次递增一个:

async Task BBB(SharedData data)
{
    await Task.Delay(TimeSpan.FromSeconds(1));
    Console.WriteLine(data.Value);
    data.Value = data.Value + 1;
}

输出:

0
1
2

【讨论】:

  • 第二段不清楚(对我来说)。 :当第一个延续通过显示一个消息框被“阻止”时,下一个延续可以运行,然后是第三个?可以请教一下步骤吗?
  • @RoyiNamir 显示一个模式消息框会阻塞当前的代码流,但它不会通过阻塞线程来做到这一点(否则应用程序将冻结)。这意味着第一个延续被卡住了,但下一个可以在 UI 线程上运行。
  • 所以当我看到第一个消息框时,当时有 2 个延续已经调用 MessageBox.Show(data.Value.ToString()); 并没有增加值?
  • @RoyiNamir 完全正确。这就是消息循环的工作方式。 Here's MSDN 论坛上的类似问题。
  • @RoyiNamir 它阻塞了流,但不阻塞线程本身。它不能阻塞线程本身,因为那是绘制消息框的线程。如果线程被阻止,应用程序将冻结并且无响应。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-08-22
  • 1970-01-01
  • 1970-01-01
  • 2017-03-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多