1. 程式人生 > >併發系列64章(非同步程式設計二)第三章

併發系列64章(非同步程式設計二)第三章

前言

是在第二章基礎上續寫的,主要是完結第二章例子部分。
請看下面幾個例子,感受一下。

報告進度

不管我們完任何app,每次更新的時候都能看到進度條。

而我們知道ui介面更新,一般來說是和更新程式非同步的,但是更新程式又要通知ui進度。

程式碼:

public class Program
{
	static double percentComplete = 0;
	static void Main(string[] args)
	{
		doit();
		Console.ReadKey();
	}

   public static void DoLoop()
	{
		Task.Run(() =>
		{
			for (var i = 1; i <= 100000; i++)
			{
				setPercent(i/1000);
				Console.WriteLine(i);
			}
			setPercent(101);
		});
	}

	public async static void doit()
	{
		var pregress = CallProcessTask();
		var task=MyMethodAsync(pregress);
		DoLoop();
		await task;
	   
	}

	public static void setPercent(int percent)
	{
		percentComplete = percent;
	}

	public static Progress<double> CallProcessTask()
	{
		var progress = new Progress<double>();
		progress.ProgressChanged += (sender, args) =>
		 {
			 //在這裡做一些進度變化,顯示在ui上。
			 Console.WriteLine("process:"+args);
		 };
		return progress;
	}

	public static Task MyMethodAsync(IProgress<double> progress=null)
	{
		  var task= Task.Run(() =>
		 {
			 while (percentComplete <= 100)
			 {
				 if (progress != null)
				 {
					 Console.WriteLine("檢視進度:" + percentComplete);
					 progress.Report(percentComplete);
				 }
			 }
		 });
		return task;
	}
}

上面我想做的事,一個非同步的事件——DoLoop。

可能初學者,很喜歡用async 和 await ,可能會認為沒有async 和 await的就不是非同步事件。

async和 await的存在的作用就在於等待當前非同步事件完成,如果沒有這個需求,那麼你為什麼要寫呢?

MyMethodAsync 是另一個非同步事件,裡面做的事監聽當前進度。

CallProcessTask是構造一個progress,並寫下委託,監聽percentComplete的改變。

等待一組任務完成

static void Main(string[] args)
{
	int[] result = DoAll().Result;
	Console.ReadKey();
}

public static async Task<int[]> DoAll()
{
	Task<int> task1 = Task.FromResult(1);
	Task<int> task2 = Task.FromResult(2);
	Task<int> task3 = Task.FromResult(3);
	int[] result=await Task.WhenAll<int>(task1, task2, task3);
	return result;
}

WhenAll 是一個新的task,管理一組Task。監聽一組task進度,當全部的task結束,這個task也結束。

使用await的時候,但是當有一個whenall 管理的task果然有多個task失敗,那麼只會報一個錯誤。

而使用whenall,我們的需求是要全部成功,要是有一個不成功那麼也應該是失敗的。所以我們只要截獲一個錯誤是正確的,系統也是這麼做的。

下面是截獲程式碼:

public static async Task<int[]> DoAll()
{
	Task<int> task1 = Task.FromResult(1);
	Task<int> task2 = Task.FromResult(2);
	Task<int> task3 = Task.FromResult(3);

	Task<int[]> Alltasks = Task.WhenAll<int>(task1, task2, task3);
	try
	{
	   var result=await Alltasks;
		return result;
	}
	catch
	{
		AggregateException allException = Alltasks.Exception;
		// 處理錯誤
	}
	return null;
}

等待任意一個任務完成

為啥會有這種需求?可能你覺得我們每次訪問的時候都是對一條url,比如說我們訪問百度,那麼我們不就是訪問www.baidu.com。

但是呢,有幾個運營服務商,做的相同的業務,都是免費的,但是公司保險起見呢,一起呼叫,為了確保在不同地域的請求穩定。

static void Main(string[] args)
{
	var result = DoAny('www.baidu.com','www.baidu.com').Result;
	Console.ReadKey();
}


public static async Task<byte[]> DoAny(string urlA,string urlB)
{
	var httpClient = new HttpClient();
	Task<Byte[]> DownLoadTaskA = httpClient.GetByteArrayAsync(urlA);
	Task<Byte[]> DownLoadTaskB = httpClient.GetByteArrayAsync(urlB);
	var completedTask =await Task.WhenAny<byte[]>(DownLoadTaskA, DownLoadTaskB);
	return await completedTask;
}

這裡值得關注的是為什麼有兩個await:

var completedTask =await Task.WhenAny<byte[]>(DownLoadTaskA, DownLoadTaskB);

return await completedTask;

因為建立WhenAny 的Task是非同步的,而建立 whenAll 的Task 是同步的。

值得注意的是whenAny 當一個任務完成時,那麼會返回這個Task。其他任務依然還是在執行,需要考慮的是中斷一個task更好,還是直接讓他執行完更好。這是需要考慮的地方。

任務完成後處理

比如說有3個任務,我希望只要完成任意一個任務完成後馬上接下來完成它的後續任務。

如果我這樣寫的話:

static void Main(string[] args)
{
	ProcessTasksAsyns();
	Console.ReadKey();
}

public static async Task ProcessTasksAsyns()
{
	Task<int> TaskA = DelayAndReturnAsync(2);
	Task<int> TaskB = DelayAndReturnAsync(3);
	Task<int> TaskC = DelayAndReturnAsync(1);
	Task<int>[] tasks = new[] { TaskA, TaskB, TaskC };
	foreach (var task in tasks)
	{
		var result = await task;
		Console.WriteLine("後續執行:"+result);
	}
}

static async Task<int> DelayAndReturnAsync(int val)
{
	await Task.Delay(TimeSpan.FromSeconds(val));
	Console.WriteLine(val);
	return val;
}

但是得到的卻是這樣的結果。

這個結果顯示Task 完成的順序是 1 ,2,3秒。

但是執行後續的順序是await的順序,也就是2,3,1.

解決方案是將3個任務,分別放在另外3個任務中執行。

static void Main(string[] args)
{
	ProcessTasksAsyns();
	Console.ReadKey();
}

public static async Task ProcessTasksAsyns()
{
	Task<int> TaskA = DelayAndReturnAsync(2);
	Task<int> TaskB = DelayAndReturnAsync(3);
	Task<int> TaskC = DelayAndReturnAsync(1);
	Task<int>[] tasks = new[] { TaskA, TaskB, TaskC };
	var processingTasks = tasks.Select(async t =>
	{
		var result = await t;
		Trace.WriteLine(result);
	});
	await Task.WhenAll(processingTasks);
}

static async Task<int> DelayAndReturnAsync(int val)
{
	await Task.Delay(TimeSpan.FromSeconds(val));
	Console.WriteLine(val);
	return val;
}

當然後面會介紹到其他方法,但是這個解釋了,如果去實現這種需求。

未完

還有幾個例子,下章結束例子