繁体   English   中英

如何使用 async 和 await 实现异步 GUI 操作?

[英]How can I realize async GUI operations with async and await?

通过单击一个按钮,我需要异步调用一个函数 f1() 来启动一个长时间的操作。 当 f1() 运行时,我想用另一个按钮启动另一个函数 f2()。 当函数 f1() 完成时,我想承认这一点。

我将整个过程分解为一个非常简单的示例,其中一个按钮启动一个长函数 writeStars(),该函数每 200 毫秒将 '*' 写入一个字符串,总计 20 次。 使用另一个按钮,我可以将“-”异步写入同一个字符串中。

        private async void btnStartThread1_Click(object sender, EventArgs e)
        {
            Thread thread1 = new Thread(writeStars);
            thread1.Start();
        }

        private void btnWriteMinus_Click(object sender, EventArgs e)
        {
            _Str += "-";
        }

        async void writeStars()
        {
            int count = 20;
            do
            {
                _Str += "*";
                Thread.Sleep(200);
            } while (count-- > 0);
        }

上面的代码给了我类似的结果:'**-****-*******-****'。

或者更快地单击第二个按钮时:'**--*-**************'。

这真的是异步的:-)

我想使用异步任务,以便能够在 writeStars() 结束时等待,然后启用一个按钮。 我试过了,但我发现没有办法用任务、异步和等待来编写这个简单的示例......

您可能应该使用asyncawait 基本上,这两个功能(和两个按钮)彼此没有任何关系,假设您没有主动阻止 GUI。

例如,您可以使用 4 个控件制作一个简单的 UI(我是在 WPF 中制作的)。 两个按钮,每个按钮调用一个异步方法,以及两个文本块,将在异步方法之后更新。

WPF 代码非常简单。 注意点击事件的名称。

<UniformGrid Columns="2">
    <Button x:Name="UpdateSlow" Content="Wait 10 seconds" Click="UpdateSlow_Click" />
    <Button x:Name="UpdateFast" Content="Wait 3 seconds" Click="UpdateFast_Click" />
    <TextBlock x:Name="LongWaitTime" Text="Waiting for text" />
    <TextBlock x:Name="ShortWaitTime" Text="Waiting for text" />
</UniformGrid>

以及后面的代码,其中包含点击事件。

private async void UpdateSlow_Click(object sender, RoutedEventArgs e)
{
    await Task.Delay(10000);
    LongWaitTime.Text = "I finished in 10 seconds";
}

private async void UpdateFast_Click(object sender, RoutedEventArgs e)
{
    await Task.Delay(3000);
    ShortWaitTime.Text = "I finished in 3 seconds";
}

如果单击“UpdateSlow”按钮,则在“LongWaitTime”文本块更改之前会有 10 秒的延迟。 同时,您可以单击“UpdateFast”按钮,这将在 3 秒后更新“ShortWaitTime”文本块。 最有可能在 10 秒等待时间结束之前完成(当然取决于您单击的时间)。

希望这是有道理的...

修改文本块:

我无法确切地弄清楚您的目标是什么,这使得很难想出一个真正好的解决方案。 如果只是为了好玩,下面的代码应该可以工作。 但是,我不确定从两种不同的方法访问相同的字符串构建是否有任何可能的副作用,因此我不建议将其用于生产。

<UniformGrid Columns="1">
    <Button x:Name="AddStar" Content="Add star to text" Click="AddStar_Click" />
    <TextBlock x:Name="Dots" Text="." Margin="20 0" VerticalAlignment="Center" FontSize="24" />
</UniformGrid>

public MainWindow()
{
    InitializeComponent();
    PrintDots();
}

public async void PrintDots()
{
    // This will run forever, printing a dot every 0.2 seconds, clearing the string, when it reaches 50 characters.
    while (true)
    {
        _sb.Append(".");
        await Task.Delay(200);

        if (_sb.Length == 50) { _sb.Clear(); }

        Dots.Text = _sb.ToString();
    }
}

private void AddStar_Click(object sender, RoutedEventArgs e)
{
    // On click a star (asterix) will be appended to the string
    _sb.Append("*");
    Dots.Text = _sb.ToString();
}

我决定将字符串保留在StringBuilder 中,因为这对于性能来说应该会好得多,并且通过使用“Append”方法来提高可读性。

一个公平的警告:

通常async void应该只用于用户界面(例如按钮点击事件)。 在大多数其他情况下,这不是进行异步编程的好方法。 一旦启动,程序将无法跟踪方法进度。 这可能会导致各种奇怪的情况,您不知道哪些方法已完成,哪些未完成。 相反,您应该使用可以返回和等待的async Task

我有点不清楚您想要什么 - 您开始说要在f1运行时启动f2 ,但随后您要求在f1结束时等待并启用f2按钮。 我要回答第二个情况下,如果你不想f2 ,直到f1已完成。

您可以使用这样的设计(借口语法或格式怪异,我已经很久没有接触 C# 领域了):

async void btnWriteStars_Click() {
  try {
    btnWriteMinus.Enabled = false;
    await writeStars();
  } finally {
    btnWriteMinus.Enabled = true;
  }
}

Task WriteStars() {
  return Task.Run(() => {
    // much stringy goodness here
  });
}

策略是“给我写星星”事件处理程序禁用您不想中断异步处理的按钮,然后等待异步处理完成,然后再次启用该按钮。

请注意,只是让WriteStars异步是不够的-它仍然会调用线程直到第一个上运行await ,当然你WriteStars没有await 所以我们使用Task.Run或类似的函数来生成一个异步任务,在其中运行同步代码块。 根据您的实际方法的作用,您可能能够将其编写为async而不是显式干预 Task 工厂 - 在任何情况下,关键是WriteStars为按钮事件处理程序返回一个 Task 对象以等待 await

如果您的要求更复杂——比如您想让button2启用状态,但将其“附加减号”操作排队,直到写完星星之后——那么您的设计也需要更复杂。 您可能能够在表单级别存储Task实例并让button2在其上执行ContinueWith ,但对于真正复杂的场景,您可能需要实现生产者-消费者方法,其中 UI 按钮将操作放在队列上,工作线程选择他们关闭。 但是对于简单的“在 Y 运行时不要做 X”的场景,禁用-等待-启用就足够了。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM