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);
                    }
                });
            }
        }
    }
  • 相关阅读:
    leetcode 86. Partition List
    leetcode 303. Range Sum Query
    leetcode 1310. XOR Queries of a Subarray
    leetcode 1309. Decrypt String from Alphabet to Integer Mapping
    leetcode 215. Kth Largest Element in an Array
    将numpy.ndarray写入excel
    leetcode 1021 Remove Outermost Parentheses
    leetcode 1306. Jump Game III
    leetcode 1305. All Elements in Two Binary Search Trees
    ICCV2019 oral:Wavelet Domain Style Transfer for an Effective Perception-distortion Tradeoff in Single Image Super-Resolution
  • 原文地址:https://www.cnblogs.com/zuowj/p/4508587.html
Copyright © 2011-2022 走看看