三、 組合任務
本示例是學習如何設置相互依賴的任務。我們學習如何創建一個任務的子任務,這個子任務必須在父任務執行結束之后,再執行。
1,示例代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ThreadTPLDemo { class Program { static void Main(string[] args) { Console.WriteLine("Task 組合操作 ————"); Task<string> task1 =CreateTask("Task1",3); Task<string> task2 = CreateTask("Task2", 2); //給task1創建一個延續操作(子操作) task1.ContinueWith(t => Console.WriteLine("task1子操作:{0},線程ID:{1},是不是線程池中的線程:{2}",
t.Result, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread),TaskContinuationOptions.OnlyOnRanToCompletion); task1.Start(); task2.Start(); Thread.Sleep(TimeSpan.FromSeconds(4)); Task task3=task2.ContinueWith(t => Console.WriteLine("task2子操作:{0},線程ID:{1},是不是線程池中的線程:{2}",
t.Result, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread),
TaskContinuationOptions.OnlyOnRanToCompletion|TaskContinuationOptions.ExecuteSynchronously); task3.GetAwaiter().OnCompleted(() => Console.WriteLine("task3異步操作完成,線程ID:{0},是不是線程池中的線程:{1}",
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread)); Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine("--------------父子任務--------------"); var task5= new Task<string>(() => { var task6 = Task.Factory.StartNew(() => TaskOper("子任務Task6", 5), TaskCreationOptions.AttachedToParent); task6.ContinueWith(t=>TaskOper("延時操作 task7",2),TaskContinuationOptions.AttachedToParent); return TaskOper("task5", 2); }); task5.Start(); while (!task5.IsCompleted) { Console.WriteLine(" task5狀態——{0}", task5.Status); Thread.Sleep(500); } Console.WriteLine(" ——task5狀態—{0}", task5.Status); string result = task5.Result; Console.WriteLine(" task5運行結果——{0}", result); Console.ReadKey(); } private static string TaskOper(string name,int seconds) { Console.WriteLine("Task 線程 ID:{0} 上,是不是線程池中的線程:{1},名稱: {2}", Thread.CurrentThread.ManagedThreadId,Thread.CurrentThread.IsThreadPoolThread, name); Thread.Sleep(TimeSpan.FromSeconds(seconds)); return string.Format("線程ID:{0},名稱:{1}:秒:{2}", Thread.CurrentThread.ManagedThreadId,name,seconds); } static Task<string> CreateTask(string name,int seconds) { return new Task<string>(() => TaskOper(name,seconds)); } } }
2.程序運行結果如下圖。
如結果所示,程序在啟動時創建了兩個任務task1與task2,并為第一個任務創建了一個子操作。啟動這兩個任務,然后等待4秒,然后給第task2運行子操作,并通過TaskContinuationOptions. OnlyOnRanToCompletion的選項嘗試同步執行這個子操作。如果子操作的運行時間非常短,則以上方式非常有用,因為放在主線程中運行比放在線程池運行要快。
如果我們注釋掉那等待4秒的代碼(藍色字體),task2這個操作就會被放到線程池中,如下圖。
接著,我們對task2任務的子操作定義了一個子操作task3,對task3使用新的GetAwaiter和Oncompleted方法,來執行一個后續操作。
最后我們創建了一個新的任務task5,通過TaskContinuationOptions. AttachedToParent選項來運行一個子任務task6與后續操作task7。
四、 將APM模式轉為任務
本示例,將上一篇(多線程編程學習筆記——線程池(一))中的(示例一線程池中調用委托)轉為任務。將APM轉換為TPL的關鍵是Task<T>.Factory.FromAsync()方法
1.代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ThreadTPLDemo { public delegate string AsyncTaskRun(string name); public delegate string IncompAsyncTaskRun(out int threadId); class Program { static void Main(string[] args) { Console.WriteLine(" 將APM模式轉為Task。。。"); int threadId = 0; AsyncTaskRun tkDele = RunTask; IncompAsyncTaskRun taskDele = RunTask; Console.WriteLine(" --------1------------"); Task<string> task1=Task<string>.Factory.FromAsync( tkDele.BeginInvoke("task1", Callback, "Task異步調用回調函數"),tkDele.EndInvoke); task1.ContinueWith(t => Console.WriteLine("task1回調函數執行結束,執行后續子操作:{0}", t.Result)); while (!task1.IsCompleted) { Console.WriteLine(" task1狀態——{0}", task1.Status); Thread.Sleep(500); } Console.WriteLine(" ——task1狀態—{0}", task1.Status); Thread.Sleep(2000); Console.WriteLine(); Console.WriteLine(" ---------------2-----------——"); Task<string> task2 = Task<string>.Factory.FromAsync(tkDele.BeginInvoke,tkDele.EndInvoke, "task2", "Task異步調用回調函數"); task2.ContinueWith(t => Console.WriteLine("task2 回調函數執行結束,執行后續子操作:{0}", t.Result)); while (!task2.IsCompleted) { Console.WriteLine(" task2狀態——{0}", task2.Status); Thread.Sleep(500); } Console.WriteLine(" ——task2狀態—{0}", task2.Status); Thread.Sleep(2000); Console.WriteLine(); Console.WriteLine(" ---------------3-----------——"); IAsyncResult r = taskDele.BeginInvoke(out threadId, Callback, "在線程池中異步調用回調函數"); Task<string> task3 = Task<string>.Factory.FromAsync(r,_=> taskDele.EndInvoke(out threadId,r)); task2.ContinueWith(t => Console.WriteLine("task3 回調函數執行結束,執行后續子操作:{0},線程ID:{1}", t.Result,threadId)); while (!task2.IsCompleted) { Console.WriteLine(" task3狀態——{0}", task2.Status); Thread.Sleep(500); } Console.WriteLine(" ——task3狀態—{0}", task2.Status); Thread.Sleep(2000); Console.WriteLine(" --------------------------——"); Thread.Sleep(2000); Console.Read(); } private static void Callback(IAsyncResult r) { Console.WriteLine("開始調用回調函數。。。"); Console.WriteLine("回調函數此時的狀態 :{0}", r.AsyncState); Console.WriteLine("調用此回調函數的線程是否在線程池 :{0}", Thread.CurrentThread.IsThreadPoolThread); Console.WriteLine("調用此回調函數的線程在線程池在的ID :{0}", Thread.CurrentThread.ManagedThreadId); } private static string RunTask(string name) { Console.WriteLine("開始工作。。。"); Console.WriteLine("調用此回調函數的線程是否在線程池 :{0}", Thread.CurrentThread.IsThreadPoolThread); Thread.Sleep(TimeSpan.FromSeconds(2)); Thread.CurrentThread.Name = name; int threadId = Thread.CurrentThread.ManagedThreadId; return string.Format("此線程的ID :{0},名稱{1}", threadId, Thread.CurrentThread.Name); } private static string RunTask(out int threadId) { Console.WriteLine("開始工作。。。"); Console.WriteLine("調用此回調函數的線程是否在線程池 :{0}", Thread.CurrentThread.IsThreadPoolThread); Thread.Sleep(TimeSpan.FromSeconds(2)); threadId = Thread.CurrentThread.ManagedThreadId; return string.Format("此線程在線程池在的ID :{0}", threadId); } } }
2.程序運行結果如下圖。
程序運行結果中可以看出來,task1中直接傳入了IAsyncResult和Func< IAsyncResult,string>,task1執行委托的異步調用,正常。
Task2與task1類似,只是使用了不同的FromAsync的方法重載,這個重載不允許指定一個將會在異步調用完成之后被調用的回調函數。我們在示例中使用后續操作代替了回調函數。如果必須使用回調函數,可以使用類似task1的調用方式。
Task3我們通過一個技巧實現了調用與FromAsync不兼容的委托。我們通過將EndInvoke封裝到一個lambda表達式中,從而適應FromAsync方法。
五、 將EAP模式轉換為任務
本示例,將上一篇(多線程編程學習筆記——線程池(三))中的(使用BackgroundWorker組件示例)轉為任務。
本示例是學習如何基于事件的異步轉換為TASK來運行。本示例的關鍵是使用TaskCompletionSource<T>,T是異步操作結果的類型。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ThreadPoolDemo { class Program { static void Main(string[] args) { Console.WriteLine(" 將EAP模式轉為Task。。。"); var tcs = new TaskCompletionSource<int>(); var worker = new BackgroundWorker(); worker.DoWork += (sender, eventArgs) => { eventArgs.Result = RunTask("后臺線程 1 ", 5); }; worker.RunWorkerCompleted += (sender, eventArgs) => { if (eventArgs.Error!=null) { Console.WriteLine(" ——出錯—{0}", eventArgs.Error); tcs.SetException(eventArgs.Error); } else if (eventArgs.Cancelled) { Console.WriteLine(" ——取消—"); tcs.SetCanceled();//取消 } else { Console.WriteLine(" ——設置結果值—{0}", eventArgs.Result); tcs.SetResult((int)eventArgs.Result); } }; worker.RunWorkerAsync(); int result = tcs.Task.Result; Console.WriteLine(" ——任務Task運行結果—{0}", result); Thread.Sleep(2000); Console.Read(); } private static int RunTask(string name,int seconds) { Console.WriteLine("Task {0} 運行在線程={1}中,是否在線程池 :{2}",name, Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsThreadPoolThread); Thread.Sleep(TimeSpan.FromSeconds(seconds)); return 42 * seconds; } } }
2.程序運行結果如下圖。
注:tcs.SetResult要封閉在try-catch中,以方便獲取異常。或者可以使用tcs.TrySetResult。
文章列表