zoukankan      html  css  js  c++  java
  • 阅读《LEARNING HARD C#学习笔记》知识点总结与摘要五

    本篇文章主要是总结异步编程的知识点,也是本系列的最后一篇文章,每一个知识点我都有写出示例代码,方便大家理解,若发现有误或不足之处还请指出,由于书中作者对此知识点讲解过于简单,所以在写这篇文章时本人参考与学习了网上许多大牛们的经验,在此感谢那些愿意分享的人们,谢谢!

    二十三.异步编程

    APM(异步编程模型):若类实现了返回类型为IAsyncResult接口的BeginXXX方法和EndXXX方法,则表明该类支持异步编程模型。如:委托类型定义了BeginInvoke与EndInvoke方法,所以所有的委托类型都实现了异步编程模型;

    调用方法代码如下(以读取文件内容为例):

    第一种方法(先调用BeginRead方法,再调用EndRead方法):

    FileStream fs = new FileStream("文件路径", FileMode.Open);
                byte[] data = new byte[fs.Length];
                IAsyncResult result = fs.BeginRead(data, 0, data.Length,null, null);
                fs.EndRead(result);
                fs.Close();
                fs.Dispose();
                System.Text.UTF8Encoding UTF8 = new System.Text.UTF8Encoding();
                string readContent = UTF8.GetString(data);
                Console.WriteLine(readContent);
                Console.Read();
    

    注意:调用EndRead方法时,会阻塞当前调用的线程,直到处理完成,若在UI线程中调用,将会出现UI假死现象;

    第二种方法(先调用BeginRead方法,再通过IAsyncResult. AsyncWaitHandle得到WaitHandle对象,然后执行WaitHandle. WaitOne方法来等待异步执行完成,最后执行EndRead方法):

    FileStream fs = new FileStream("文件路径", FileMode.Open);
                byte[] data = new byte[fs.Length];
                IAsyncResult result = fs.BeginRead(data, 0, data.Length, null, null);
                WaitHandle readWait=result.AsyncWaitHandle;
                readWait.WaitOne();
    fs.EndRead(result);
                System.Text.UTF8Encoding UTF8 = new System.Text.UTF8Encoding();
                string readContent = UTF8.GetString(data);
                Console.WriteLine(readContent);
                Console.Read();
    

    注意:调用WaitOne方法时,会阻塞当前调用的线程,直到处理完成,若在UI线程中调用,将会出现UI假死现象;

    第三种方法(先调用BeginRead方法,再循环判断IAsyncResult. IsCompleted,最后执行EndRead方法):

    FileStream fs = new FileStream("文件路径", FileMode.Open);
                byte[] data = new byte[fs.Length];
                IAsyncResult result = fs.BeginRead(data, 0, data.Length, null, null);
                while(!result.IsCompleted)
                {
                    Thread.Sleep(1000);
                }
                fs.EndRead(result);
                System.Text.UTF8Encoding UTF8 = new System.Text.UTF8Encoding();
                string readContent = UTF8.GetString(data);
                Console.WriteLine(readContent);
                Console.Read();
    

    注意:循环判断IsComplete方法时,会阻塞当前调用的线程,直到处理完成[即:IsCompleted=true],若在UI线程中循环判断,将会出现UI假死现象;

    第四种方法(先定义回调方法,然后再调用BeginRead方法,调用时传入异步回调方法委托实例及相关数据):

    FileStream fs = new FileStream("文件路径", FileMode.Open);
                byte[] data = new byte[fs.Length];
                IAsyncResult result = fs.BeginRead(data, 0, data.Length, new AsyncCallback(ReadCallback), data);
                Console.Read();
    
    //回调方法
    static void ReadCallback(IAsyncResult ar)
            {
                WaitHandle readWait = ar.AsyncWaitHandle;
                byte[] data =( byte[])ar.AsyncState;
                System.Text.UTF8Encoding UTF8 = new System.Text.UTF8Encoding();
                string readContent = UTF8.GetString(data);
                Console.WriteLine(readContent);
            }
    

    说明:第四种由于采用异步委托方法获取得结果,所以不会阻塞调用线程,也就不会出现UI假死现象,当然前面三种方法也可以将耗时逻辑(等待及获取结果)放在方法中,然后在执行时开启新线程,这样可以达到与第四种相同的方法,但个人建议若在UI主线程中实现异步调用,优先考虑采用第四种方法;

    EAP(基于事件的异步模式):实现了EAP的类具有一个或多个以Async为后缀的方法,以及对应的Completed事件,并支持异步方法的取消与进度报告。

    EAP通过事件、AsyncOperationManager类和AsyncOperation类两个帮助器类实现如下功能:

    1)   异步执行耗时的任务。

    2)   获得进度报告和增量结果。

    3)   支持耗时任务的取消。

    4)   获得任务的结果值或异常信息。

    5)   更复杂:支持同时执行多个异步操作、进度报告、增量结果、取消操作、返回结果值或异常信息。

    对于相对简单的多线程应用程序,BackgroundWorker组件提供了一个简单的解决方案。对于更复杂的异步应用程序,可以考虑实现一个符合基于事件的异步模式的类。

    可参见这篇博文:http://www.cnblogs.com/heyuquan/archive/2013/04/01/2993085.html

    以下是BackgroundWorker组件在控制台程序中的使用方法:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static int Main(string[] args)
            {
                Console.Write("Main Thread ID:{0}
    ", System.Threading.Thread.CurrentThread.ManagedThreadId);
                int workTimes = 100;
                BackgroundWorker bgWorker = new BackgroundWorker();
                bgWorker.WorkerSupportsCancellation = true;//设置支持异步取消
                bgWorker.WorkerReportsProgress = true;//设置支持报告进度
    
                bgWorker.DoWork += bgWorker_DoWork;
                bgWorker.ProgressChanged += bgWorker_ProgressChanged;
                bgWorker.RunWorkerCompleted += bgWorker_RunWorkerCompleted;
                bgWorker.RunWorkerAsync(workTimes);//启动异步执行
                string input = Console.ReadLine();
                if (input == "c" && bgWorker.IsBusy)
                {
                    bgWorker.CancelAsync();
                }
                Console.Read();
                return 0;
            }
    
            static void bgWorker_DoWork(object sender, DoWorkEventArgs e)
            {
                //在新线程中处理该方法
                BackgroundWorker bgWorker = sender as BackgroundWorker;
                int workTimes = Convert.ToInt32(e.Argument);
                for(int i = 0; i <= workTimes;i++ )
                {
                    if (bgWorker.CancellationPending) //如果取消了
                    {
                       e.Cancel = true;
                       Console.Write("ThreadID:{0}-->Cancel!
    ", System.Threading.Thread.CurrentThread.ManagedThreadId);
                        break;
                    }
                    else
                    {
                        Console.Write("Thread ID:{0}-->WorkTimes:{1}
    ", System.Threading.Thread.CurrentThread.ManagedThreadId, i);
                        bgWorker.ReportProgress(i);//投告进度,引发ProgressChanged事件
                        System.Threading.Thread.Sleep(1000);
                    }
                }
            }
    
    
            static void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
            {
                //当进度改变时在新线程中调用该方法,可直接操作控件
                Console.Write("Thread ID:{0}-->Progress:{1}
    ", System.Threading.Thread.CurrentThread.ManagedThreadId, e.ProgressPercentage);
            }
    
    
            static void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
            {
                //当DoWork方法处理完成后(不论是否为取消)在新线程中调用此方法
                Console.Write("Thread ID:{0}-->Completed!", System.Threading.Thread.CurrentThread.ManagedThreadId);
            }
        }
    }
    

    TAP(基于任务的异步模式):一般使用Task类来实现基于任务的异步模式编程,实现步骤如下:

    1. 通过调用Task类指定的构造函数来实例化Task对象,如:Task task = new Task(Action委托实例,CancellationToken实例);注意:Task类有许多重载的构造函数,可依据实际情况进行调用
    2. 调用Task实例对象的Start方法启动异步执行构造函数参数中Action委托实例所引用的方法,如:task.Start()

    可参见这篇博文:http://www.cnblogs.com/heyuquan/archive/2013/04/18/Task-based-Asynchronous-Pattern.html

    具体的使用代码示例如下(为了让新手也能看懂,所以此处我采用标准的委托实例方法来写,大家可以使用Lambda表达式来写,那样代码量就会精简一些):

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication1
    {
        public partial class Form4 : Form
        {
            CancellationTokenSource cts = new CancellationTokenSource(); //用于发出与接收任务取消信息
            public Form4()
            {
                InitializeComponent();
            }
    
            private void Form4_Load(object sender, EventArgs e)
            {
                TestTAP();
            }
    
            private void TestTAP()
            {
                WriteMsg(string.Format( "Main Thread ID:{0}", Thread.CurrentThread.ManagedThreadId));
                SynchronizationContext syncContext = SynchronizationContext.Current; //获取当前同步上下文,用于在异步中可操作UI上的控件等
                Task task = new Task(new Action<object>(TAPAsyncMethod), syncContext);
                task.Start();
            }
    
            private void TAPAsyncMethod(object arg)
            {
                CancellationToken ct = cts.Token;
                SynchronizationContext sc = arg as  SynchronizationContext;
    
                SendOrPostCallback callback = new SendOrPostCallback(WriteMsg);
                int threadId = Thread.CurrentThread.ManagedThreadId;
    
                for (int i = 1; i <= 100; i++)
                {
                    if (ct.IsCancellationRequested)
                    {
                        sc.Post(callback, string.Format("Thread ID:{0}发出消息:任务取消了!", threadId));
                        break;
                    }
                    int n = i + (i+1);
                    sc.Post(callback, string.Format("Thread ID:{0},发出消息:第{1}次执行结果:{2}", threadId, i, n));
                    Thread.Sleep(500);
                }
            }
    
            private void WriteMsg(object state)
            {
               listBox1.Items.Add(string.Format( "Thread ID:{0}显示:[{1}]", Thread.CurrentThread.ManagedThreadId, state));
               listBox1.SelectedIndex = listBox1.Items.Count - 1;
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                //发送取消命令
                cts.Cancel();
            }
        }
    }
    

    C# 5.0中使用async和await关键字来实现异步编程,在这两个关键字的帮助下,我们可以使用同步编程的思想方式来实现异步编程,定义异步方法只需在定义普通方法的前面加上async关键字,在方法体指定的语句前加上await关键字实现异步处理耗时的代码。async和await关键字不会让调用的方法运行在新线程中,而是将方法以await关键字来作为界限分割成多个片段,并使其中的一些片段以异步方式执行。

    注意:

    1.只有标记了async关键字的方法或lambda表达式才能在方法体中使用await关键字;

    2. 方法用async关键字标记不会影响方法是同步还是异步运行并完成,即:若方法用async关键字,但方法体中并没有包含await关键字,则仍以同步方式运行并完成;

    3.只有返回类型为void、Task或Task<TResult>的方法才能用async关键字标记,但除如下情况:

    A.程序的入口方法(Main方法);B.标记为同步的方法([MethodImpl(MethodImplOptions.Synchronized)]);C.包含ref或out参数的方法;D. Lambda被用作表达式树时;

    4.只有方法的返回类型为可等待的类型(即类型必需包含公共的GetAwaiter() 方法并且返回有效的TaskAwaiter,如:Task、Task<TResult>)才能用await关键字标记;

    可参见这篇博文:http://www.cnblogs.com/heyuquan/archive/2012/11/30/2795859.html

    具体的使用代码示例如下:

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication1
    {
        public partial class Form5 : Form
        {
            public Form5()
            {
                InitializeComponent();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                listBox1.Items.Add(string.Format("Main Thread ID:{0}", Thread.CurrentThread.ManagedThreadId));
                AsyncMethod();
            }
    
            private async void AsyncMethod()
            {
                int threadId = Thread.CurrentThread.ManagedThreadId;
                SynchronizationContext syncContext = SynchronizationContext.Current;
                await ExecBody(syncContext);//此处会开新线程,并且下面的代码将在该处执行完成后才会执行。
                listBox1.Items.Add("方法执行完成了!");
                listBox1.SelectedIndex = listBox1.Items.Count - 1;
            }
    
            private Task ExecBody(SynchronizationContext sc)
            {
              return  Task.Run(() =>
                {
                    int threadId = Thread.CurrentThread.ManagedThreadId;
                    for (int i = 1; i <= 100; i++)
                    {
                        int n = i + (i + 1);
                        sc.Post(state =>
                        {
                            listBox1.Items.Add(string.Format("Thread ID:{0}显示:[{1}]", Thread.CurrentThread.ManagedThreadId, state));
                            listBox1.SelectedIndex = listBox1.Items.Count - 1;
                        }, string.Format("Thread ID:{0},发出消息:第{1}次执行结果:{2}", threadId, i, n));
                        Thread.Sleep(500);
                    }
                });
            }
        }
    }
  • 相关阅读:
    光棍节奉献10款chart插件 节日快乐.
    平安夜分享2款下拉菜单(CSS版本和JQuery版本)
    MVC 中T4扫盲贴
    解析JQuery 的Bind()事件
    Null在从数据库读取的时候的一点点小阴谋
    JQuery 动画效果集锦
    VS2010支持的6款UML简介
    T4系列文章之2:T4工具简介、调试以及T4运行原理
    既然结构也可以继承接口
    css float 之 clear
  • 原文地址:https://www.cnblogs.com/zuowj/p/4508587.html
Copyright © 2011-2022 走看看