1. 程式人生 > >C#沉澱-非同步程式設計 二

C#沉澱-非同步程式設計 二

針對於await表示式的異常處理

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

namespace CodeForAsync
{

    class Program
    {
        //定義一個非同步方法
        static async Task BadAsync()
        {
            try
            {
                //故意丟擲一個異常
await Task.Run(() => { throw new Exception(); }); } catch (Exception ex) { //捕獲異常 Console.WriteLine("捕獲一個非同步"+ex.ToString()); } } static void Main(string[] args) { Task t =
BadAsync(); t.Wait(); Console.WriteLine("檢視非同步方法的狀態:"+t.Status); Console.WriteLine("檢視是否有未經處理的異常:"+t.IsFaulted); Console.ReadKey(); } } }

輸出:

捕獲一個非同步System.Exception: 引發型別為“System.Exception”的異常。
   在 CodeForAsync.Program.<BadAsync>b__0() 位置 f:\我的文件\文件\C# 沉澱\CodeForAsync\CodeForAsync\Program.cs:行號 18
   在 System.Threading.Tasks.Task`1.InnerInvoke()
   在 System.Threading.Tasks.Task.Execute()
--- 引發異常的上一位置中堆疊跟蹤的末尾 ---
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   在 CodeForAsync.Program.<BadAsync>d__2.MoveNext() 位置 f:\我的文件\文件\C# 沉澱\CodeForAsync\CodeForAsync\Program.cs:行號 18
檢視非同步方法的狀態:RanToCompletion
檢視是否有未經處理的異常:False

從輸出結果來看,Task的狀態為RanToCompletion,說明即使捕獲到異常也不會令Task終止或取消;IsFaulted的狀態為False,說明沒有未處理的異常

在呼叫方法中同步的等待任務

Task提供了一個例項方法Wait,可以在Task上呼叫該方法,同步的等待任務的完成

示例:

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

namespace CodeForAsync
{
    static class MyDownloadString
    {
        public static void DoRun()
        {
            Task<int> t1 = CountCharactersAsync(1, "http://baidu.com");

            //等待任務結束
            t1.Wait();
            Console.WriteLine("任務已經結束,值:" + t1.Result);
        }

        //下載網站資源
        private static async Task<int> CountCharactersAsync(int id, string uristring)
        {
            string result = await (new WebClient()).DownloadStringTaskAsync(new Uri(uristring));
            return result.Length;
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            MyDownloadString.DoRun();

            Console.ReadKey();
        }
    }
}

這裡的Wait用在了單一的Task物件上,也可以用於一組Task上,但是需要用到Task上的兩個靜態方法:

示例:WaitAllWaitAny

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

namespace CodeForAsync
{
    class MyDownloadString
    {
        Stopwatch sw = new Stopwatch();

        public void DoRun()
        {
            //下載百度資源
            Task<int> t1 = CountCharactersAsync(1, "http://baidu.com");
            //下載搜狗資源
            Task<int> t2 = CountCharactersAsync(2, "https://pinyin.sogou.com/");

            ///WaitAll會等待所包含的所有的Task都完成後才會往下執行
            ///WaitAny會等待所包含的所有的Taks中只要有一個完成即會向下執行
            Task<int>[] tasks = new Task<int>[] { t1, t2 };
            //Task.WaitAll(tasks);
            Task.WaitAny(tasks);

            ///下面的寫法也是允許的
            ///這兩個靜態方法接收的是Taks型別的陣列
            ///下面實現了隱式的轉換
            //Task.WaitAll(t1, t2);
            //Task.WaitAny(t1, t2);

            Console.WriteLine("任務一是否完成:" + (t1.IsCompleted ? "完成" : "未完成").ToString());
            Console.WriteLine("任務二是否完成:" + (t2.IsCompleted ? "完成" : "未完成").ToString());
        }

        //下載網站資源
        private async Task<int> CountCharactersAsync(int id, string uristring)
        {
            WebClient wc = new WebClient();
            string result = await wc.DownloadStringTaskAsync(new Uri(uristring));

            return result.Length;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            MyDownloadString ds = new MyDownloadString();
            ds.DoRun();

            Console.ReadKey();
        }
    }
}

輸出:

任務一是否完成:完成
任務二是否完成:未完成

WaitAll與WaitAny有許多過載,這裡簡單介紹兩個:

//等待所有任務完成,或CancellationToken發出了取消訊號
public static void WaitAll(Task[] tasks, CancellationToken cancellationToken);

//等待所有任務完成,如果在等待時間內仍未完成,則繼續往下執行
public static bool WaitAll(Task[] tasks, int millisecondsTimeout);
public static bool WaitAll(Task[] tasks, TimeSpan timeout);

//等待所有任務完成,如果發生超時或者有取消動作,則繼續往下執行不再等待
public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken);

/////////////////////////////////////////

//等待一個任務完成,或CancellationToken發出了取消訊號
public static void WaitAny(Task[] tasks, CancellationToken cancellationToken);

//等待一個任務完成,如果在等待時間內仍未完成,則繼續往下執行
public static bool WaitAny(Task[] tasks, int millisecondsTimeout);
public static bool WaitAny(Task[] tasks, TimeSpan timeout);

//等待一個任務完成,如果發生超時或者有取消動作,則繼續往下執行不再等待
public static bool WaitAny(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken);

在非同步方法中非同步的等待任務

await會等待一個或所有的任務完成,

可以通過Task.WhenAllTask.WhenAny方法來實現。這兩個方法稱為組合子

示例:

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

namespace CodeForAsync
{
    class MyDownloadString
    {
        public void DoRun()
        {
            //下載百度資源
            Task<int> t = CountCharactersAsync("http://baidu.com", "https://pinyin.sogou.com");
            Console.WriteLine("任務t是否已經完成:{0}", t.IsCompleted ? "完成" : "未完成");
            Console.WriteLine("輸出值:{0}", t.Result);
        }

        //下載網站資源
        private async Task<int> CountCharactersAsync(string uristring1, string uristring2)
        {
            WebClient wc1 = new WebClient();
            WebClient wc2 = new WebClient();

            Task<string> t1 = wc1.DownloadStringTaskAsync(new Uri(uristring1));
            Task<string> t2 = wc2.DownloadStringTaskAsync(new Uri(uristring2));

            List<Task<string>> tasks = new List<Task<string>>();
            tasks.Add(t1);
            tasks.Add(t2);
			
            //await Task.WhenAll(tasks);
            await Task.WhenAny(tasks);

            Console.WriteLine("任務一是否完成:{0}", t1.IsCompleted ? "完成" : "未完成");
            Console.WriteLine("任務二是否完成:{0}", t2.IsCompleted ? "完成" : "未完成");

            return t1.IsCompleted ? t1.Result.Length : t2.Result.Length;

        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            MyDownloadString ds = new MyDownloadString();
            ds.DoRun();

            Console.ReadKey();
        }
    }
}

輸出:

任務t是否已經完成:未完成
任務一是否完成:完成
任務二是否完成:未完成
輸出值:81

await 會等待一個Task任務的完成,如果await 呼叫Task.WhenAll,則會非同步的等待所有方法都被呼叫完;而await呼叫Task.WhenAny時,則會非同步的等待其中一個任務完成;同樣,當代碼遇到await時,會返回到呼叫方法上去

上例中使用了Task.WhenAny(tasks);,所以,當任務一完成而任務二未完成時,程式碼便向下執行了,不再等待,如果換成Task.WhenAll(tasks);,則程式碼會等待兩個任務完成後才會向下執行

WaitAll/WaitAny/WhenAll/WhenAny之間的區別

最好理解的是WaitAllWaitAny,它們都是發生在呼叫層的等待,阻塞當前執行緒,等待所有的非同步方法有所返回以後才會繼續當前的執行緒

WhenAllWhenAny也是等待,不過不是在呼叫層的執行緒中等待,而是在非同步方法裡等待,所以這裡說它是“非同步的等待”,它阻塞的是非同步方法的內部過程,而呼叫方法因為遇到await會反加到呼叫層,所以呼叫層的執行緒不會被阻塞

也就是說它們的效果是一樣的,但所在的包裝層級不同

Task.Delay方法

Task.Delay方法會使任務暫停一段時間,我們知道Thread.Sleep也可以將程式暫停一段時間,不同的是,Task.Delay就在非同步方法內部使用,不會導致呼叫層執行緒阻塞,而Thread.Sleep即使是在非同步方法內部他用,也可能使用呼叫層被阻塞

示例:

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

namespace CodeForAsync
{
    class MyClass
    {
        public async void Hello()
        {
            Console.WriteLine("節點1");

            Thread.Sleep(1000);
            // await Task.Delay(1000);

            await Task.Run(() =>
            {
                Console.WriteLine("節點2");
            });
        }

    }

    class Program
    {
        static void Main(string[] args)
        {
            MyClass mc = new MyClass();
            mc.Hello();

            Thread.Sleep(100);
            Console.WriteLine("節點3");

            Console.ReadKey();
        }
    }
}

輸出:

節點1
節點2
節點3

Thread.Sleep(1000);會將當前執行緒與呼叫層的執行緒都阻塞,如果使用await Task.Delay(1000);的話,輸出如下:

節點1
節點3
節點2