美文网首页
异步编程(十):async/await模式之GUI程序

异步编程(十):async/await模式之GUI程序

作者: 曦航老袁 | 来源:发表于2019-02-24 23:48 被阅读0次

首先我们应该清楚的理论知识:GUI程序在设计上要求所有的显示变化都必须在主GUI线程中完成,如点击按钮、展示标签、移动窗体等。Windows程序是通过消息来实现这一点的,消息被放入由 消息泵 管理的消息队列中。消息泵从队列中取出一条消息,并调用它的处理程序(handler)代码。当处理程序代码完成时,才会获取下一条消息并循环这个过程。
由于这种架构,处理程序代码就必须短小精悍(这里是指执行时间短,而不是代码长度短哟),这样才不至于挂起并阻碍其他GUI行为的处理。如果某个消息的处理程序耗时过长,消息队列中的消息就会产生积压。程序也就失去了响应,因为在那个长时间运行的处理程序完成之前,是无法处理任何消息的。

下面我们先建立一个WPF的窗口

<Window x:Class="MessagePump.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MessagePump"
        Title="Pump" Height="200" Width="400">
    <StackPanel>
        <Label Name="lblStatus" Margin="10,5,10,0">Not Doing Anything</Label>
        <Button Name="btnDostuff" Content="Do Stuff" HorizontalAlignment="Left" Margin="10,5" Padding="5,2" Click="btnDoStuff_Click"/>
    </StackPanel>
</Window>

窗口的支持C#代码如下:

using System.Threading;
using System.Windows;

namespace MessagePump
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private void btnDoStuff_Click(object sender, RoutedEventArgs e)
        {
            btnDostuff.IsEnabled = false;
            lblStatus.Content = "Doing Stuff";
            Thread.Sleep(4000);
            lblStatus.Content = "Not Doing Anything";
            btnDostuff.IsEnabled = true;
        }
    }
}

很明显开发者希望在按钮按下的4秒内,改变按钮的状态及标签的提示信息。然而事实并非如此。当按钮点击按钮后,什么都没有发生。而且在这段时间内窗体完全被“冻结”,按钮没有被禁用,标签没有被修改,甚至不能进行任何操作了。

这显然不是开发者需要的效果,那么为什么会发生这种情况呢?

这个奇怪行为的原因其实非常简单。点击按钮时,按钮的Click消息放入消息队列。消息泵从队列中移除该消息并开始处理点击按钮的处理程序代码,也就是btnDoStuff_Click方法。在处理程序本身退出(即休眠4秒并退出)之前,这些消息都无法执行,然后所有行为都恢复了,由于速度太快肉眼根本看不见。呵呵...

解决方案:如果处理程序能将前两条消息压入队列,然后将自己从处理器上摘下,在4秒之后再将自己压入队列,那么这些以及所有其他消息都可以在等待的时间内被处理,整个过程就会如之前预料的那样,并且还能保持响应。
具体实施:我们可以使用async/await特性轻松地实现这一点。当到达await语句时,处理程序返回调用方法,并从处理器上摘下。让其他程序得以处理——包括处理程序已经压入队列的那两条。在空闲任务完成后,后续部分又被重新安排到线程上。
代码实现:

private async void btnDoStuff_Click(object sender, RoutedEventArgs e)
{
    btnDostuff.IsEnabled = false;
    lblStatus.Content = "Doing Stuff";
    await Task.Delay(10000);
    lblStatus.Content = "Not Doing Anything";
    btnDostuff.IsEnabled = true;
}

除了延时等待之外,我们还可以使用 Task.Yield 方法创建一个立即返回的awaitable。等待一个Yield可以让异步方法在执行后续部分返回到调用方法。可以理解成离开当前的消息队列,回到队列末尾,让处理器有时间处理其他任务。

static class DoStuff
{
    public static async Task<int> FindSeriesSum(int i1)
    {
        int sum = 0;
        for (int i = 0; i < i1; i++)
        {
            sum += 1;
            if (1 % 1000 == 0) await Task.Yield();
        }
        return sum;
    }
}
class Program
{
    static void Main()
    {
        Task<int> value = DoStuff.FindSeriesSum(1000000);
        CountBig(100000);
        CountBig(100000);
        CountBig(100000);
        CountBig(100000);
        Console.WriteLine("Sum: {0}", value.Result);
    }

    private static void CountBig(int p)
    {
        for (int i = 0; i < p; i++) ;
    }
}

特别提示:Yield方法在GUI程序中非常有用,可以中断大量的工作,让其他任务随时使用处理器。

相关文章

网友评论

      本文标题:异步编程(十):async/await模式之GUI程序

      本文链接:https://www.haomeiwen.com/subject/vlyiyqtx.html