美文网首页简书面面观
C#沉淀-异步编程 三

C#沉淀-异步编程 三

作者: 东南有大树 | 来源:发表于2018-10-01 21:40 被阅读13次

GUI程序在设计上要求所有的显示变化都必须在主GUI线程中完成,Windows程序是通过消息来实现这一点的,消息被放入由消息泵管理的消息队列中。

消息泵从列队中取出一条消息,并调用它的处理程序代码。当程序代码完成时,消息泵获取下一条消息并循环这个过程。

由于这个架构,处理程序代码就必须矮小精悍,这样才不至于扶起并阻碍其他GUI行为处理。如果某个消息的处理程序代码耗时过长,消息队列中的消息会产生积压。程序将失去响应,因为在那个长时间运行的处理程序完成之前,无法处理任何消息。

示例:下面创建一个WPF程序,在主窗体中添加一个Label与一个Button,具体工作在后端代码的注释中

<Window x:Class="WpfForAsync.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfForAsync"
        mc:Ignorable="d"
        Title="MainWindow" Height="151.4" Width="323.2">
    <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>

后端代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfForAsync
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void btnDoStuff_Click(object sender, RoutedEventArgs e)
        {
            //首先让按钮禁用
            btnDoStuff.IsEnabled = false;
            //第二步将lable的文本改动
            lblStatus.Content = "Doing Stuff";
            //阻塞4秒钟
            Thread.Sleep(1000 * 4);
            //将lable的内容改为原来的内容
            lblStatus.Content = "Not Doing Anything";
            //将按钮设置为可用
            btnDoStuff.IsEnabled = true;
        }
    }
}

事实上,当点击按钮的时候,窗体没有任何变化,Label也不会发生内容更改的情况,而且,主窗体在被阻塞的4秒内也无被冻结了!!!

来看程序执行的顺序:

  1. btnDoStuff_Click将[禁用按钮]消息压入队列
  2. btnDoStuff_Click将[改变文本]消息压入队列
  3. btnDoStuff_Click将被阻塞4秒
  4. btnDoStuff_Click将[改变文本]消息压入队列
  5. btnDoStuff_Click将[启用按钮]消息压入队列
  6. btnDoStuff_Click将退出
  7. 程序执行[禁用按钮]
  8. 程序执行[改变文本]
  9. 程序执行[改变文本]
  10. 程序执行[启用按钮]

由于7~10发生的太快,我们根本看不到发生了什么!

示例:使用异步来解决上述问题

后端代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfForAsync
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void btnDoStuff_Click(object sender, RoutedEventArgs e)
        {
            //首先让按钮禁用
            btnDoStuff.IsEnabled = false;
            //第二步将lable的文本改动
            lblStatus.Content = "Doing Stuff";

            await Task.Delay(1000 * 4);

            //将lable的内容改为原来的内容
            lblStatus.Content = "Not Doing Anything";
            //将按钮设置为可用
            btnDoStuff.IsEnabled = true;
        }
    }
}

将btnDoStuff_Click方法改为一个异步方法,异步方法内部使用await Task.Delay(1000 * 4);,即将遇到await的时候,程序会返回到调用方法,处理程序将从处理器上摘下,禁用按钮、改变文本将被处理;当4秒之后,处理程序将自己再压入队列,再将之后的改变文本、启用按钮压入列队,当处理程序退出后,这两个子消息也将被执行,但在休息的4秒期间,是可以手动窗体的

Task.Yield

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

示例:

using System;
using System.Net;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading;

namespace CodeForAsync
{
    static class DoStuff
    {
        public static async Task<int> FindSeriesSum(int x)
        {
            int sum = 0;
            for (int i = 1; i < x; i++)
            {
                sum += i;
                Console.WriteLine("Yield"+i);
                if (i % 10 == 0)
                    await Task.Yield();
            }

            return sum;
        }
    }

    class Program
    {
        private static void CountBig(int p)
        {
            for (int i = 0; i < p; i++)
                Console.WriteLine("CountBig"+i); 
        }

        static void Main(string[] args)
        {
            Task<int> value = DoStuff.FindSeriesSum(1000);
            CountBig(1000);
            CountBig(1000);
            CountBig(1000);
            CountBig(1000);
            Console.WriteLine("Sum:{0}",value.Result);
            Console.ReadKey();
        }
    }
}

当程序遇到await Task.Yield();时,会将当前的执行权转让出来,之后的CountBig(1000);便可以提前得到执行

使用异步Lambda表达式

语法示例:

btn.Click += async (sender, e) => {//处理工作};

完整代码示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfForAsync
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            this.btnDoStuff.Click += async (sender, e) =>
            {
                btnDoStuff.IsEnabled = false;
                lblStatus.Content = "Hello";
                await Task.Delay(4000);
                btnDoStuff.IsEnabled = true;
                lblStatus.Content = "World";
            };
        }

    }
}

一个完整的GUI示例,请自行尝试

<Window x:Class="WpfForAsync.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfForAsync"
        mc:Ignorable="d"
        Title="MainWindow" Height="151.4" Width="323.2" Loaded="Window_Loaded">
    <StackPanel>
        <Button Name="btnProcess" Width="100" Click="btnProcess_Click">Process</Button>
        <Button Name="btnCancel" Width="100" Click="progressBar_Click" >Cancel</Button>
        <ProgressBar Name="progressBar" Height="20" Width="200" Margin="10" HorizontalAlignment="Right"></ProgressBar>
    </StackPanel>
</Window>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading.Tasks;

namespace WpfForAsync
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        CancellationTokenSource _cancellationTokenSource;
        CancellationToken _cancellationToken;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {

        }

        private async void btnProcess_Click(object sender, RoutedEventArgs e)
        {
            btnProcess.IsEnabled = false;

            _cancellationTokenSource = new CancellationTokenSource();
            _cancellationToken = _cancellationTokenSource.Token;

            int completedPercent = 0;
            for (int i = 0; i < 10; i++)
            {
                if (_cancellationToken.IsCancellationRequested)
                    break;
                try
                {
                    await Task.Delay(500,_cancellationToken);
                    completedPercent = (i+1) * 10;
                }
                catch (TaskCanceledException ex)
                {
                    completedPercent = i * 10;
                }

                progressBar.Value = completedPercent;
            }
            string message = _cancellationToken.IsCancellationRequested ? string.Format("Process was cancelled at {0}%",completedPercent) : "Process completed normally";
            MessageBox.Show(message,"Completion Status");

            progressBar.Value = 0;
            btnProcess.IsEnabled = true;
            btnCancel.IsEnabled = true;
        }

        private void progressBar_Click(object sender, RoutedEventArgs e)
        {
            if (!btnProcess.IsEnabled)
            {
                btnCancel.IsEnabled = false;
                _cancellationTokenSource.Cancel();
            }
        }
    }
}

相关文章

  • C#沉淀-异步编程 三

    GUI程序在设计上要求所有的显示变化都必须在主GUI线程中完成,Windows程序是通过消息来实现这一点的,消息被...

  • C#沉淀-异步编程 四

    BackgroundWorker类 async/await特性适合那些需要在后台完成的不相关的小任务,有时候,需要...

  • C#沉淀-异步编程 一

    什么是异步 任务以固定的顺序被执行叫做同步,任务不按固定顺序执行则叫做异步 关于进程与线程 启动程序时,系统会在内...

  • C#沉淀-异步编程 二

    针对于await表达式的异常处理 输出: 从输出结果来看,Task的状态为RanToCompletion,说明即使...

  • 关于C#异步编程你应该了解的几点建议

    前段时间写了一篇关于C#异步编程入门的文章,你可以点击《C#异步编程入门看这篇就够了》查看。这篇文章我们来讨论下关...

  • 诸子学游戏 学习服务器5

    , C#异步编程https://msdn.microsoft.com/zh-cn/magazine/ff95920...

  • C#异步编程

    什么是异步编程 什么是异步编程呢?举个简单的例子: 上面这段代码中,Main方法中的代码是按照自上而下的顺序执行的...

  • ASP.NET 谨用 async/await

    C# 5.0 引入 async/await 关键字,旨在简化异步编程模型,抛去语法糖就是 Net4.0 的 Tas...

  • Step by step:一起来学C# (2)

    提纲: (1)C#的编程环境介绍 (2)C#的几个语言要素 (3)第一个C#的窗体程序 一、编程环境 C#的编程使...

  • C#中的异步编程

    假如我有6个Task任务,我需要等这6个任务都执行完了才能去执行主任务,这很简单,就是普通的顺序写代码就好了: 但...

网友评论

    本文标题:C#沉淀-异步编程 三

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