3.C#异步编程的几种方式

目录

 

1.异步定义

2.async/await构建异步方法

2.1直观认识

2.2 异步方法详解

2.3异步操作的取消

3.使用BackgroundWorker类构建异步方法

3.并行循环

4.传统旧模式


1.异步定义

异步程序中,程序代码不需要按照编写时的顺序严格执行。为了改善代码性能,有时需要在一个新的线程中运行一部分代码,有时无需创建新的线程,但为了更好的利用单个线程的能力,需要改变代码的执行顺序。

简而言之:异步编程提供代码的非顺序执行能力。让程序能够在部分耗时操作的同时,干其它的事情。

2.async/await构建异步方法

async/await构建异步方法是C# 5.0引入的新特性。是几种异步方法中最简练的一种。

2.1直观认识

示例代码:

//异步操作示例类

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

namespace ASyncTest
{
    class MyDownloadString
    {
        Stopwatch st = new Stopwatch();
        Stopwatch st1 = new Stopwatch();

        ///


        /// 普通方式
        ///

        public void DoRun()
        {
            const int LargeNum = 600000;
            st.Start();
            int t1 = CountCharacters(1, "http://www.microsoft.com");
            int t2 = CountCharacters(2, "http://www.csdn.net");
            CountToALargeNumber(1, LargeNum);
            CountToALargeNumber(2, LargeNum);
            CountToALargeNumber(3, LargeNum);
            CountToALargeNumber(4, LargeNum);

            Console.WriteLine("http://www.microsoft.com 网站字符数:{0}", t1);
            Console.WriteLine("http://www.csdn.net 网站字符数:{0}", t2);
            st.Stop();
        }

        ///


        /// 异步方式
        ///

        public void DoRunAsync()
        {
            const int LargeNum = 600000;
            st1.Start();
            Task t1 = CountCharactersAnSyn(1, "http://www.microsoft.com");
            Task t2 = CountCharactersAnSyn(2, "http://www.csdn.net");
            CountToALargeNumber2(1, LargeNum);
            CountToALargeNumber2(2, LargeNum);
            CountToALargeNumber2(3, LargeNum);
            CountToALargeNumber2(4, LargeNum);

            Console.WriteLine("http://www.microsoft.com 网站字符数:{0}", t1.Result);
            Console.WriteLine("http://www.csdn.net 网站字符数:{0}", t2.Result);
        }
        private void CountToALargeNumber(int id, int largeNum)
        {
            for (long i = 0; i < largeNum; i++)
            { }
            Console.WriteLine(" 结束计数ID{0}: {1,4:N0}ms", id, st.Elapsed.TotalMilliseconds);
        }

        private int CountCharacters(int id, string uriString)
        {
            WebClient wc1 = new WebClient();
            Console.WriteLine("开始调用{0}: {1,4:N0} ms", id, st.Elapsed.TotalMilliseconds);
            string result = wc1.DownloadString(new Uri(uriString));
            Console.WriteLine("   调用{0}完成:{1,4:N0} ms", id, st.Elapsed.TotalMilliseconds);
            return result.Length;
        }

        private async Task CountCharactersAnSyn(int id, string uriString)
        {
            WebClient wc1 = new WebClient();
            Console.WriteLine("开始调用{0}: {1,4:N0} ms", id, st1.Elapsed.TotalMilliseconds);
            string result =await wc1.DownloadStringTaskAsync(new Uri(uriString));
            Console.WriteLine("   调用{0}完成:{1,4:N0} ms", id, st1.Elapsed.TotalMilliseconds);
            return result.Length;
        }
        private void CountToALargeNumber2(int id, int largeNum)
        {
            for (long i = 0; i < largeNum; i++)
            { }
            Console.WriteLine(" 结束计数ID{0}: {1,4:N0}ms", id, st1.Elapsed.TotalMilliseconds);
        }
    }
}
 

========================================

using System;


namespace ASyncTest
{
    class Program
    {
        static void Main(string[] args)
        {
            MyDownloadString mds = new MyDownloadString();
            mds.DoRun();
            Console.WriteLine("==========================================");
            mds.DoRunAsync();
            Console.ReadKey();
        }        
    }
}

运行结果:
3.C#异步编程的几种方式_第1张图片

异步方式耗时约为普通方式的1/2以下(不同运行存在差异)。

2.2 异步方法详解

异步方法在完成其工作之前就返回到调用方法,然后在调用方法继续执行其他部分的时候完成工作。

异步方法具备的特点:

  • 方法头包括async修饰符。
  • 包含一个或多个awaitbiao表达式,表示可以异步完成的任务。
  • 方法返回值必须为void,Task,Task之一。
  • 方法的参数可以为任意类型,但不能为out,ref参数
  • 调用方法通过返回值的Result获取

2.3异步操作的取消

 

3.使用BackgroundWorker类构建异步方法

async/await特性更适合在hout后台完成的小任务

有时候我们需要在后台新建一个线程默默完成一项工作,过程中时不时同主线程进行通信,这就是BackgroundWorker类的主要任务。

3.C#异步编程的几种方式_第2张图片

BackgroundWorker类的使用比较简单。参考样例即可。

======================================================

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace BackgroundWorkTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bw = (sender as BackgroundWorker);
            for(int i=1;i<11;i++)
            {
                if (bw.CancellationPending)
                {
                    e.Cancel = true;
                    break;
                }
                else
                {
                    bw.ReportProgress(i * 10);
                    Thread.Sleep(500);
                }
            }
            
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar1.Value = e.ProgressPercentage;
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            progressBar1.Value = 0;
            if(e.Cancelled)
            {
                MessageBox.Show("进程被取消", "取消进程");
            }
            else
            {
                MessageBox.Show("进程完成", "完成");
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if(!backgroundWorker1.IsBusy)
            {
                backgroundWorker1.RunWorkerAsync();
            }            
        }

        private void button2_Click(object sender, EventArgs e)
        {
            backgroundWorker1.CancelAsync();
        }
    }
}
 

3.C#异步编程的几种方式_第3张图片

3.并行循环

 

 

4.传统旧模式

4.1 委托的引用方法

除了上述异步模式,传统方法是使用委托的BeginInvoke,EndInvoke方法;

如果委托对象在调用列表中只有一个方法(引用方法),它就可以异步执行这个方法。委托类有BeginInvoke,EndInvoke方法,可以如下方式使用:

  • 当调用BeginInvoke方法时,它开始在一个独立线程上执行引用方法,并立即返回到原始线程。原始线程可以继续运行,而引用方法会在线程池的线程中并行执行。
  • 当程序希望获取已完成的异步方法的结果时,可以检查BeginInvoke返回的IAsyncResult的IsCompleted属性,或者调用EndInvoke方法等待委托的完成。

使用这一过程有三种标准模式。区别在于,原始线程如何知道发起的线程已经完成。

3.C#异步编程的几种方式_第4张图片

  • 等待一直到完成模式(wait until done)

原始线程发起异步方法并做了一些其他处理后,原始线程中断并等待异步方法完成后再继续。

  • 轮询模式(polling)

原始线程定期检查发起的异步方法线程是否完成,如果没有则继续做其他事情。

  • 回调模式(callback)

原始线程一直执行,无需等待,当发起的线程中引用方法完成后,发起的线程就调用回调方法,由回调方法在调用EndInvoke之前处理异步方法的结果。

4.2 三种模式的实现

4.2.1 BeginInvoke、EndInvoke使用

  • BeginInvoke

如上所述,委托对象调用列表只有一个方法时,可通过BeginInvoke方法异步执行。该方法接受委托方法的参数列表外,还接受其它额外参数。返回值为一个IAsyncResult类型的值,该值包含有关新线程的信息。

3.C#异步编程的几种方式_第5张图片

  • EndInvoke

EndInvoke方法用来获取异步调用返回的值,并释放线程资源。

该方法接收一个IAsyncResult参数引用,即BeginInvoke方法的返回值,通过该参数找到关联线程。

如果线程池中线程已退出,则该方法清理线程状态并释放资源;如果线程在继续,则停止线程并等待,直到资源清理完毕并返回值。

每一个BeginInvoke确保有一个EndInvoke调用;

4.2.2 wait until done

      //代码片段 

        private delegate string GetContent(string uri);
        GetContent content;
        IAsyncResult invokeResult;
        private string GetWebContent(string uri)
        {
            WebClient client = new WebClient();
            return client.DownloadString(new Uri(uri));
        }

            invokeResult =content.BeginInvoke("http://www.csdn.net",null,null);//具有引用方法的content委托对象
            string webstr = content.EndInvoke(invokeResult);
            this.label1.Text = "http://www.csdn.net contents length:"+webstr.Length.ToString();

即调用线程通过委托对象的BeginInvoke启动线程后,再接着通过EndInvoke结束线程,获取返回值。

4.2.3 polling

不停的探测BeginInvoke方法返回值的IsCompleted属性。

invokeResult = content.BeginInvoke("http://www.csdn.net", null, null);
            while (!invokeResult.IsCompleted)
            {
                //do something!
            }
            string webstr = content.EndInvoke(invokeResult);
            this.label1.Text = "http://www.csdn.net contents length:" + webstr.Length.ToString();

4.2.2 callback

初始线程通过BeginInvoke启动异步方法后,就自己管自己了。当异步方法完成后,系统调用一个用户自定义的方法处理结果,并且调用EndInvoke方法。这个自定义方法称为回调方法或回调。

BeginInvoke方法最后两个额外参数由回调方法使用,第一个参数AsyncCallback是回调参数的名称,第二个参数是要传入回调方法的对象引用。

有多种方法为BeginInvoke提供AsyncCallback参数。一种是直接提供一个AsyncCallback委托给BeginInvoke,另一种是只提供名称,由系统自动创建AsyncCallback委托。

3.C#异步编程的几种方式_第6张图片

问题来了,只提供名称,自动创建回调委托,回调委托的具体工作怎么自定义?

 

5.结语

在应用程序中实现多线程的首选方式是使用 BackgroundWorker 组件。 BackgroundWorker 组件使用事件驱动模型实现多线程。 后台线程运行 DoWork 事件处理程序,而创建控件的线程运行 ProgressChanged 和 RunWorkerCompleted 事件处理程序。 可以从 ProgressChanged 和 RunWorkerCompleted 事件处理程序调用控件。

当实现的功能比较小时,可以用async/wait特性实现。传统方法比较繁琐,且易于出错。

你可能感兴趣的:(3.C#异步编程的几种方式)