程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 閱讀《LEARNING HARD C#學習筆記》知識點總結與摘要五,

閱讀《LEARNING HARD C#學習筆記》知識點總結與摘要五,

編輯:C#入門知識

閱讀《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}\n", 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!\n", System.Threading.Thread.CurrentThread.ManagedThreadId);
                    break;
                }
                else
                {
                    Console.Write("Thread ID:{0}-->WorkTimes:{1}\n", 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}\n", 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類來實現基於任務的異步模式編程,實現步驟如下:

可參見這篇博文: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);
                }
            });
        }
    }
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved