C#之使用任务并行库

.NET Framework4.0引入了一个新的关于异步操作的API,它叫做任务并行库(Task Parallel Library,简称TPL)。TPL的核心是任务,一个任务代表一个异步操作,该操作可以通过多种方式运行,可以使用或不使用独立线程运行。

一个任务可以它通过多种方式与其他方式组合起来,TPL与之前的模式相比,其中一个关键优势就在于其具有用于组合任务的便利的API。

处理任务中的异常结果有多种方式。由于一个任务可能由其他任务组成,这些任务也可能拥有各自的子任务,所以有一个AggregateException的概念。这种异常可以捕获底层任务内部的所有异常,并允许单独处理这些异常。

创建任务

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ThreadDemo
{
    class Proram8
    {
        static void TaskMethod(string name)
        {
            Console.WriteLine($"Task {name} is running on a thread id" +
                $"{Thread.CurrentThread.ManagedThreadId}. Is thread pool thread:" +
                $"{Thread.CurrentThread.IsThreadPoolThread}");
        }
        static void Main()
        {
            var t1 = new Task(() => TaskMethod("Task 1"));
            var t2 = new Task(() => TaskMethod("Task 2"));
            t2.Start();
            t1.Start();
            Task.Run(() => TaskMethod("Task 3"));
            Task.Factory.StartNew(() => TaskMethod("Task4"));
            Task.Factory.StartNew(() => TaskMethod("Task5"),TaskCreationOptions.LongRunning);
            Thread.Sleep(TimeSpan.FromSeconds(1));
            
        }
    }
}

output:

Task Task 3 is running on a thread id5. Is thread pool thread:True
Task Task 2 is running on a thread id3. Is thread pool thread:True
Task Task4 is running on a thread id7. Is thread pool thread:True
Task Task5 is running on a thread id8. Is thread pool thread:False
Task Task 1 is running on a thread id4. Is thread pool thread:True

创建任务主要有两种方式,(1)用其构造函数,然后调用Start()方法(2)用Task的静态方法:Run(),Factory.StartNew(),静态方法会立即开始工作,而Run()只是Factory.StartNew()的一个快捷方式,后者有附加选项,如果我们对此附加选项标记为长时间运行,则该任务将不会使用线程池

使用任务执行基本的操作

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
    class Program9
    {
        static int TaskMethod(string name)
        {
            Console.WriteLine($"Task {name} is running on a thread id" +
                $"{Thread.CurrentThread.ManagedThreadId}. Is thread pool thread:" +
                $"{Thread.CurrentThread.IsThreadPoolThread}");
            Thread.Sleep(TimeSpan.FromSeconds(2));
            return 42;
        }
        static Task<int> CreateTask(string name)
        {
            return new Task<int>(() => TaskMethod(name));
        }
        static void Main()
        {
            TaskMethod("Main Thread Task");//并没有封装到任务中,就是主线程普通的函数
            var task = CreateTask("Task 1");
            task.Start();
            var result = task.Result;//该行为会阻塞主线程
            Console.WriteLine($"Result:{result}");

            task = CreateTask("Task 2");
            task.RunSynchronously();//该任务会运行在主线程上,可以避免使用线程池来执行非常短暂的操作
            result = task.Result;
            Console.WriteLine($"Resultt:{result}");

            task = CreateTask("Task 3");
            Console.WriteLine(task.Status);//打印Start之前的状态
            task.Start();
            while (task.IsCompleted)
            {
                Console.WriteLine(task.Status);
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
            }
            Console.WriteLine(task.Status);
            result = task.Result;//阻塞主线程
            Console.WriteLine($"Result is {result}");

        }
    }
}

output:

Task Main Thread Task is running on a thread id1. Is thread pool thread:False
Task Task 1 is running on a thread id3. Is thread pool thread:True
Result:42
Task Task 2 is running on a thread id1. Is thread pool thread:False
Resultt:42
Created
Task Task 3 is running on a thread id4. Is thread pool thread:True
Running
Result is 42

组合任务

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
    class Program10
    {
        static int TaskMethod(string name,int seconds)
        {
            Console.WriteLine($"Task {name} is running on a thread id" +
                $"{Thread.CurrentThread.ManagedThreadId}.Is thread pool thread:" +
                $"{Thread.CurrentThread.IsThreadPoolThread}");
            Thread.Sleep(TimeSpan.FromSeconds(seconds));
            return 42 * seconds;
        }
        static void Main()
        {
            var firstTask = new Task<int>(() => TaskMethod("First Task", 3));
            var secondTask = new Task<int>(() => TaskMethod("Second Task", 2));
            firstTask.ContinueWith(
                t => Console.WriteLine($"The first answer is{t.Result}" +
                $"{Thread.CurrentThread.ManagedThreadId},is thread pool thread:" +
                $"{Thread.CurrentThread.IsThreadPoolThread}"),
                TaskContinuationOptions.OnlyOnRanToCompletion
                ) ;
            firstTask.Start();
            secondTask.Start();
            Thread.Sleep(TimeSpan.FromSeconds(4));

            Task continuation = secondTask.ContinueWith(t=>Console.WriteLine($"The second answer is" +
                $"{t.Result}. Thread id" +
                $"{Thread.CurrentThread.ManagedThreadId},is thread pool thread:" +
                $"{Thread.CurrentThread.IsThreadPoolThread}"),
                TaskContinuationOptions.OnlyOnRanToCompletion|TaskContinuationOptions.ExecuteSynchronously
                );
            continuation.GetAwaiter().OnCompleted(()=>Console.WriteLine($"" +
                $"Continuation Task completed! Thread id" +
                $"{Thread.CurrentThread.ManagedThreadId},is thread pool thread:" +
                $"{Thread.CurrentThread.IsThreadPoolThread}"));
            Thread.Sleep(TimeSpan.FromSeconds(2));
            Console.WriteLine();
            firstTask = new Task<int>(()=>
            {
                var innerTask = Task.Factory.StartNew(()=>TaskMethod("Second Task",5),TaskCreationOptions.AttachedToParent);
                innerTask.ContinueWith(t => TaskMethod("Third Task", 2), TaskContinuationOptions.AttachedToParent);
                return TaskMethod("First Task", 2);
            });
            firstTask.Start();
            while (!firstTask.IsCompleted)
            {
                Console.WriteLine(firstTask.Status);
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
            }
            Console.WriteLine(firstTask.Status);
            Thread.Sleep(TimeSpan.FromSeconds(10));
        }
    }
}

output:

Task First Task is running on a thread id3.Is thread pool thread:True
Task Second Task is running on a thread id4.Is thread pool thread:True
The first answer is1263,is thread pool thread:True
The second answer is84. Thread id1,is thread pool thread:False
Continuation Task completed! Thread id4,is thread pool thread:True

Running
Task Second Task is running on a thread id5.Is thread pool thread:True
Task First Task is running on a thread id6.Is thread pool thread:True
Running
Running
Running
WaitingForChildrenToComplete
WaitingForChildrenToComplete
WaitingForChildrenToComplete
WaitingForChildrenToComplete
WaitingForChildrenToComplete
WaitingForChildrenToComplete
Task Third Task is running on a thread id4.Is thread pool thread:True
WaitingForChildrenToComplete
WaitingForChildrenToComplete
WaitingForChildrenToComplete
WaitingForChildrenToComplete
RanToCompletion

将APM模式转换为任务

先简单复习下APM的基本情况:

APM,即异步编程模式,这是第一代异步编程的范式,它主要是依靠委托来实现的。

当委托对象在调用列表中只有一个方法(后称为引用方法),它就可以异步执行这个方法。委托类有两个方法,叫做BeginInvoke,EndInvoke,而APM的whole story如下:

  • 当我们调用BeginInvoke时,它开始在一个独立线程上执行引用方法,并且立即返回到原始线程,返回IAsyncResult类,原始线程可以继续,而引用方法会在线程池的线程中并行执行
  • 当程序希望获取已经完成的异步结果时,包括检查引用方法在线程池的执行状态时,可以以检查BeginInvoke返回的IAsyncResult类的IsCompleted属性,或者调用EndInvoke方法来等待委托完成。
  • EndInvoke方法接受一个由BeginInvoke方法返回的IAsyncResult对象的引用,并且释放线程使用的资源,所以必须确保对每一个BeginInvoke都有一个对应的EndIInvoke

由此也衍生了三种标准模式:

  • wait-until-done模式:原始线程发起一个异步方法的调用,做一些处理,然后停止并等待,直到开启的线程结束。这主要是通过在原始线程中,调用var result=delegate.EndInvoke(IAsyncResult)来实现的。Demo如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ThreadDemo
{
    delegate long MyDel(int first, int second);//声明委托
    class Program12
    {
        static long sum(int first,int second)
        {
            Thread.Sleep(100);
            Console.WriteLine("This is inside Sum");
            return first + second;
        }
        static void Main()
        {
            MyDel del = new MyDel(sum);
            Console.WriteLine("Before BeginInvoke....");
            var iar = del.BeginInvoke(3, 5, null, null);//前两个参数是委托的参数,后两个中,第一个是回调函数,第二个object state
            Console.WriteLine("After caling BeginInvoke..");
            Console.WriteLine("Doing staff...");
            long result = del.EndInvoke(iar);//阻塞原始线程,直到异步引用方法完成
            Console.WriteLine("After EndInvoke:{0}", result);

        }
    }
}

output:

Before BeginInvoke....
After caling BeginInvoke..
Doing staff...
This is inside Sum
After EndInvoke:8
  • 轮询模式:原始线程发起了异步方法的调用,做一些处理,然后使用IAsyncResult的IsComplete属性来定期检查开启的线程是否完成,如果完成,原始线程就调用EndInvoke清理,最后继续做其他事情,否则,它做一些其他处理,过一会再检查。Demo如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ThreadDemo
{
    delegate long MyDel(int first, int second);//声明委托
    class Program12
    {
        static long sum(int first,int second)
        {
            Thread.Sleep(100);
            Console.WriteLine("This is inside Sum");
            return first + second;
        }
        static void Main()
        {
            MyDel del = new MyDel(sum);
            Console.WriteLine("Before BeginInvoke....");
            var iar = del.BeginInvoke(3, 5, null, null);//前两个参数是委托的参数,后两个中,第一个是回调函数,第二个object state
            Console.WriteLine("After caling BeginInvoke..");
            while (!iar.IsCompleted) //轮询是否完成
            {
                //如果没完成就做如下操作
                Console.WriteLine("Not Done...");
                for (long i = 0; i < 1000000; i++)
                    ;
            }
            //完成
            Console.WriteLine("Done!...");
            var result = del.EndInvoke(iar);
            Console.WriteLine("Result:{0}", result);
        }
    }
}

output:

Before BeginInvoke....
After caling BeginInvoke..
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
Not Done...
This is inside Sum
Not Done...
Done!...
Result:8

对比以上两个模式的demo,可以发现,轮询模式实际上就是在wait-until-done模式基础让原始线程在异步引用方法操作未完成之前做某些操作,而后者实际缺少了轮询,只能“干巴巴”的等待异步的完成。

  • 回调模式:前两种模式均为原始线程继续它自己的控制流程,直到它直到开启的线程已经完成,然后获取结果,并继续。回调模式的不同之处在于,一旦原始线程发起了异步方法,它就自己管自己了,不再考虑同步。而异步的引用方法调用结束后,系统调用一个用户自定义的方法来处理将结果,并且调用委托的EndInvoke方法,这个用户自定义的 方法就叫做回调方法。Demo如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ThreadDemo
{
    delegate long MyDel(int first, int second);//声明委托
    class Program12
    {
        static long sum(int first,int second)
        {
            Thread.Sleep(100);
            Console.WriteLine("This is inside Sum");
            return first + second;
        }
        //回调函数
        static void CallWhenDone(IAsyncResult iar)
        {
            Console.WriteLine("Inside CallWhenDone");
            AsyncResult ar = (AsyncResult)iar;//将传入的iar强制转换为AsyncResult类
            MyDel del = (MyDel)ar.AsyncDelegate;//将AsyncResult类强制转换为委托类,AsyncDelegate
            var result = del.EndInvoke(iar);//清理
            Console.WriteLine("The result is {0}", result);
        }
        static void Main()
        {
            MyDel del = new MyDel(sum);
            Console.WriteLine("Before BeginInvoke....");
            var iar = del.BeginInvoke(3, 5, new AsyncCallback(CallWhenDone), null);//前两个参数是委托的参数,后两个中,第一个是回调函数,第二个object state
            Console.WriteLine("Doing more work in Main");
            Thread.Sleep(500);
            Console.WriteLine("Done with Main,Exiting");
        }
    }
}

output:

Before BeginInvoke....
Doing more work in Main
This is inside Sum
Inside CallWhenDone
The result is 8
Done with Main,Exiting

下面学习将APM模式转换为任务:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
    class Program11
    {
        delegate string AsynchronousTask(string threadName);
        delegate string IncompatibleAsynchronousTask(out int threadId);
        static void Callback(IAsyncResult ar)
        {
            Console.WriteLine("Startting a callback...");
            Console.WriteLine($"State passed to a callback:{ar.AsyncState}");
            Console.WriteLine($"Is thread pool thread:" +
                $"{Thread.CurrentThread.IsThreadPoolThread}");
            Console.WriteLine($"Thread pool worker thread id:{Thread.CurrentThread.ManagedThreadId}");
        }
        static string Test(string threadName)
        {
            Console.WriteLine("Starting ....");
            Console.WriteLine($"Is thread pool thread:" +
                $"{Thread.CurrentThread.IsThreadPoolThread}");
            Thread.Sleep(TimeSpan.FromSeconds(2));
            Thread.CurrentThread.Name = threadName;
            return $"Thread name:{Thread.CurrentThread.Name}";
        }
        static string Test(out int threadId)
        {
            Console.WriteLine("Startting...");
            Console.WriteLine($"Is thread poool :{Thread.CurrentThread.IsThreadPoolThread}");
            Thread.Sleep(TimeSpan.FromSeconds(2));
            threadId = Thread.CurrentThread.ManagedThreadId;
            return $"Thread pool worker thread id:{threadId}";
        }
        public static void Main()
        {
            int threadId;
            AsynchronousTask d = Test;
            IncompatibleAsynchronousTask e = Test;

            Console.WriteLine("Option 1:");
            Task<string> task = Task<string>.Factory.FromAsync(
                d.BeginInvoke("AsyncTaskThread", Callback, "a delegate asynchronous call"), d.EndInvoke);
            task.ContinueWith(t => Console.WriteLine($"" +
                $"Callback is finished,now running a continuation!Result:{t.Result}"));
            while (!task.IsCompleted)
            {
                Console.WriteLine(task.Status);
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
            }
            Console.WriteLine(task.Status);
            Thread.Sleep(TimeSpan.FromSeconds(1));
            Console.WriteLine("----------------------------------------------------");
            Console.WriteLine();
            Console.WriteLine("Option 2:");
            task = Task<string>.Factory.FromAsync(d.BeginInvoke, d.EndInvoke, "AsyncTaskThread", "a delegate asynchronous call");
            task.ContinueWith(t => Console.WriteLine($"Task is completed,now running a continuation!Resultt:{t.Result}"));
            while (!task.IsCompleted)
            {
                Console.WriteLine(task.Status);
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
            }
            Console.WriteLine(task.Status);
            Thread.Sleep(TimeSpan.FromSeconds(1));
            Console.WriteLine("-------------------------------------------------------");
            Console.WriteLine();
            Console.WriteLine("Option 3:");
            IAsyncResult ar = e.BeginInvoke(out threadId, Callback, "a delegate asynchronous call");
            task = Task<string>.Factory.FromAsync(ar, _ => e.EndInvoke(out threadId, ar));
            task.ContinueWith(t =>
            Console.WriteLine($"Task is completed,now running a continuation!" +
            $"Result:{t.Result},threadId:{threadId}"));
            while (!task.IsCompleted)
            {
                Console.WriteLine(task.Status);
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
            }
            Console.WriteLine(task.Status);
            Thread.Sleep(TimeSpan.FromSeconds(1));
        }
    }
}

output:

Option 1:
Starting ....
Is thread pool thread:True
WaitingForActivation
WaitingForActivation
WaitingForActivation
WaitingForActivation
Startting a callback...
State passed to a callback:a delegate asynchronous call
Is thread pool thread:True
Thread pool worker thread id:3
Callback is finished,now running a continuation!Result:Thread name:AsyncTaskThread
RanToCompletion
----------------------------------------------------

Option 2:
WaitingForActivation
Starting ....
Is thread pool thread:True
WaitingForActivation
WaitingForActivation
WaitingForActivation
Task is completed,now running a continuation!Resultt:Thread name:AsyncTaskThread
RanToCompletion
-------------------------------------------------------

Option 3:
WaitingForActivation
Startting...
Is thread poool :True
WaitingForActivation
WaitingForActivation
WaitingForActivation
Startting a callback...
State passed to a callback:a delegate asynchronous call
Is thread pool thread:True
Thread pool worker thread id:3
Task is completed,now running a continuation!Result:Thread pool worker thread id:3,threadId:3
RanToCompletion

将APM转换为TPL的关键点是Task<T>.Factory.FromAsync,T是异步操作结果的类型,该方法有数个重载。第一个例子中传入了IAsyncResultFunc<IAsyncResult,string>,实现无缝衔接,将BeginInvoke的返回值,在异步操作结束后,赋值给EndInvoke。除此之外,还组合了任务,体现了TPL的优越性。

第二个例子,使用了不同的FromAsync方法,不允许采用回调函数,但我们可以使用后续操作替代它,但如果回调函数很重要,可以使用第一个例子的方法。

第三个例子,由于Task中不接受out参数,因此必须先单独BeginInvoke,而后将其返回值带入FromAsync方法中,然后将EndInvoke封装到一个lambda表达式中,从而适合任务工厂方法。

将EAP模式转换为任务

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.ComponentModel;

namespace ThreadDemo
{
    class Program13
    {
        static TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
        static int TaskMethod(string name,int seconds)
        {
            Console.WriteLine($"" +
                $"Task {name} is running on a thread id" +
                $"{Thread.CurrentThread.ManagedThreadId}.Is thread a pool thread" +
                $"{Thread.CurrentThread.IsThreadPoolThread}");
            Thread.Sleep(TimeSpan.FromSeconds(seconds));
            return 42 * seconds;
        }
        static void Main()
        {
            
            var worker = new BackgroundWorker();
            worker.DoWork += dowork; ;//绑定事件
            worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
            worker.RunWorkerAsync();
            var result = tcs.Task.Result;
            Console.WriteLine($"Result is {result}");
        }

        private static void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                tcs.SetException(e.Error);
            }
            else if (e.Cancelled)
            {
                tcs.SetCanceled();
            }
            else
            {
                tcs.SetResult((int)e.Result);
            }
                
        }

        private static void dowork(object sender, DoWorkEventArgs e)
        {
            e.Result = TaskMethod("Background worker", 5);
        }
    }
}

output:

Task Background worker is running on a thread id3.Is thread a pool threadTrue
Result is 210

实现取消

取消机制同样是运用CancellationTokenSourceCancellationToken类进行控制,其中后者是前者的Token属性,前者通过调用Cancel方法,后者的IsCancellationRequested属性就被设置为True。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
    class Program14
    {
        static int TaskMethod(string name,int seconds,CancellationToken token)
        {
            Console.WriteLine($"" +
                $"Task {name} is running on a thread id" +
                $"{Thread.CurrentThread.ManagedThreadId}. Is" +
                $"Thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}");

            for(int i = 0; i < seconds; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));
                if (token.IsCancellationRequested) return -1;
            }
            return 42 * seconds;
        }
        static void Main()
        {
            var cts = new CancellationTokenSource();
            var longTask = new Task<int>(() => TaskMethod("Task 1", 10, cts.Token), cts.Token);
            Console.WriteLine(longTask.Status);
            cts.Cancel();
            Console.WriteLine(longTask.Status);
            Console.WriteLine("First task has been cancelled before execution");

            cts = new CancellationTokenSource();
            longTask = new Task<int>(() => TaskMethod("Task 2", 10, cts.Token), cts.Token);
            longTask.Start();
            for(int i = 0; i < 5; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine(longTask.Status);
            }
            cts.Cancel();
            for(int i = 0; i < 5; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine(longTask.Status);
            }
            Console.WriteLine($"A task has been completed with result {longTask.Result}");
        }
       
    }
}

output:

Created
Canceled
First task has been cancelled before execution
Task Task 2 is running on a thread id3. IsThread pool thread:True
Running
Running
Running
Running
Running
RanToCompletion
RanToCompletion
RanToCompletion
RanToCompletion
RanToCompletion
A task has been completed with result -1

仔细看longTask的创建代码,我们给底层任务传递一次取消标志,然后给任务的构造函数再传递一次,这样做的目的是,如果在任务执行前就取消它,那么它根本就不会运行,这得益于构造函数中传入的取消标志,如果取消了,再试图对该任务调用Start方法,就会得到一个InvalidOperationException异常。

这里需要注意的是,当任务运行后,我们再对其进行取消,任务的状态是RanToCompletion,因为从TPL的角度来看,该任务正常完成了它的工作,这与任务根本没有执行就被取消的Cancel状态是不同的。

处理任务中的异常

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
    class Program15
    {
        static int TaskMethod(string name,int seconds)
        {
            Console.WriteLine($"Task {name} is running on a thread id" +
                $"{Thread.CurrentThread.ManagedThreadId}. Is thread pool thread:" +
                $"{Thread.CurrentThread.IsThreadPoolThread}");
            Thread.Sleep(TimeSpan.FromSeconds(seconds));
            throw new Exception("Boom...");
            return 42 * seconds;
        }
        static void Main()
        {
            Task<int> task;
            try
            {
                task = Task.Run(() => TaskMethod("Task 1", 2));
                var result = task.Result;
                Console.WriteLine($"Result:{result}");
            }
            catch(Exception e)
            {
                Console.WriteLine($"Exception caught:{e}");
            }
            Console.WriteLine("-------------------------------------------------------");
            try
            {
                task = Task.Run(() => TaskMethod("Task 2", 2));
                var result = task.GetAwaiter().GetResult();
                Console.WriteLine($"Result:{result}");
            }
            catch(Exception e)
            {
                Console.WriteLine($"Exception cautht:{e}");
            }
            Console.WriteLine("-------------------------------------------------------");
            var t1 = new Task<int>(() => TaskMethod("Task 3", 3));
            var t2 = new Task<int>(() => TaskMethod("Task 4", 2));
            var complexTask = Task.WhenAll(t1, t2);
            var exceptionHandler = complexTask.ContinueWith(t => Console.WriteLine($"Exception caught:{t.Exception}"), TaskContinuationOptions.OnlyOnFaulted);
            t1.Start();
            t2.Start();
            Thread.Sleep(TimeSpan.FromSeconds(5));
        }
    }
}

output:

Task Task 1 is running on a thread id3. Is thread pool thread:True
Exception caught:System.AggregateException: 发生一个或多个错误。 ---> System.Exception: Boom...
   在 ThreadDemo.Program15.TaskMethod(String name, Int32 seconds) 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program15.cs:行号 17
   在 ThreadDemo.Program15.<>c.<Main>b__1_3() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program15.cs:行号 25
   在 System.Threading.Tasks.Task`1.InnerInvoke()
   在 System.Threading.Tasks.Task.Execute()
   --- 内部异常堆栈跟踪的结尾 ---
   在 System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   在 System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
   在 System.Threading.Tasks.Task`1.get_Result()
   在 ThreadDemo.Program15.Main() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program15.cs:行号 26
---> (内部异常 #0) System.Exception: Boom...
   在 ThreadDemo.Program15.TaskMethod(String name, Int32 seconds) 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program15.cs:行号 17
   在 ThreadDemo.Program15.<>c.<Main>b__1_3() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program15.cs:行号 25
   在 System.Threading.Tasks.Task`1.InnerInvoke()
   在 System.Threading.Tasks.Task.Execute()<---

-------------------------------------------------------
Task Task 2 is running on a thread id4. Is thread pool thread:True
Exception cautht:System.Exception: Boom...
   在 ThreadDemo.Program15.TaskMethod(String name, Int32 seconds) 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program15.cs:行号 17
   在 ThreadDemo.Program15.<>c.<Main>b__1_4() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program15.cs:行号 36
   在 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`1.GetResult()
   在 ThreadDemo.Program15.Main() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program15.cs:行号 37
-------------------------------------------------------
Task Task 4 is running on a thread id4. Is thread pool thread:True
Task Task 3 is running on a thread id3. Is thread pool thread:True
Exception caught:System.AggregateException: 发生一个或多个错误。 ---> System.Exception: Boom...
   在 ThreadDemo.Program15.TaskMethod(String name, Int32 seconds) 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program15.cs:行号 17
   在 ThreadDemo.Program15.<>c.<Main>b__1_0() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program15.cs:行号 45
   在 System.Threading.Tasks.Task`1.InnerInvoke()
   在 System.Threading.Tasks.Task.Execute()
   --- 内部异常堆栈跟踪的结尾 ---
---> (内部异常 #0) System.Exception: Boom...
   在 ThreadDemo.Program15.TaskMethod(String name, Int32 seconds) 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program15.cs:行号 17
   在 ThreadDemo.Program15.<>c.<Main>b__1_0() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program15.cs:行号 45
   在 System.Threading.Tasks.Task`1.InnerInvoke()
   在 System.Threading.Tasks.Task.Execute()<---

---> (内部异常 #1) System.Exception: Boom...
   在 ThreadDemo.Program15.TaskMethod(String name, Int32 seconds) 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program15.cs:行号 17
   在 ThreadDemo.Program15.<>c.<Main>b__1_1() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program15.cs:行号 46
   在 System.Threading.Tasks.Task`1.InnerInvoke()
   在 System.Threading.Tasks.Task.Execute()<---
    请按任意键继续. . .

并行运行任务

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
    class Program16
    {
        static int TaskMethod(string name,int seconds)
        {
            Console.WriteLine($"" +
                $"Task {name} is running on  a thread id" +
                $"{Thread.CurrentThread.ManagedThreadId}. Is tthread pool thread:" +
                $"{Thread.CurrentThread.IsThreadPoolThread}");
            Thread.Sleep(TimeSpan.FromSeconds(seconds));
            return 42 * seconds;
        }
       static void Main()
        {
            var firstTask = new Task<int>(() => TaskMethod("Task one", 3));
            var secondTask = new Task<int>(() => TaskMethod("Task two", 2));
            var whenAllTask = Task.WhenAll(firstTask, secondTask);//创建了第三个任务,该任务将会在所有任务完成后运行,该任务的结果提供了 一个 结果数组。
            whenAllTask.ContinueWith(t =>
            Console.WriteLine($"The first answer is {t.Result[0]}, the second answer is {t.Result[1]}"), TaskContinuationOptions.OnlyOnRanToCompletion);

            firstTask.Start();
            secondTask.Start();
            Thread.Sleep(TimeSpan.FromSeconds(4));

            var tasks = new List<Task<int>>();
            for(int i = 1; i < 4; i++)
            {
                int counter = i;
                var task = new Task<int>(() => TaskMethod($"Task {counter}", counter));
                tasks.Add(task);
                task.Start();
            }
            while (tasks.Count > 0)
            {
                var completedTask = Task.WhenAny(tasks).Result;
                tasks.Remove(completedTask);
                Console.WriteLine($"A task has been completed with result {completedTask.Result}");
            }
            Thread.Sleep(TimeSpan.FromSeconds(1));
        }
    }
}

output:

Task Task one is running on  a thread id3. Is tthread pool thread:True
Task Task two is running on  a thread id4. Is tthread pool thread:True
The first answer is 126, the second answer is 84
Task Task 3 is running on  a thread id3. Is tthread pool thread:True
Task Task 1 is running on  a thread id5. Is tthread pool thread:True
Task Task 2 is running on  a thread id4. Is tthread pool thread:True
A task has been completed with result 42
A task has been completed with result 84
A task has been completed with result 126

Task.WaitAll是创建 了一个新的任务,本例中类型为Task<int[]>,返回的结果是一个int数组,ContinueWith中传入的第一个参数是lambda表达式,该表达式的参数正是任务本身。

Task.WaitAny是传入任务列表中的任何已经完成的一个任务。

需要注意的是在使用for循环,创建Task时候,需要有中间变量int countter=i,如果直接在Task中,使用i,则可能会重复。因为如果是中间变量counter,则迫使传入Task的是每次循环的i.

使用TaskScheduler配置任务的执行

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TextBlock x:Name="ContentTextBlock" HorizontalAlignment="Left"
                   Margin="44,134,0,0" VerticalAlignment="Top" Width="425" Height="40"/>
        <Button Content="Sync" HorizontalAlignment="Left" Margin="45,190,0,0" VerticalAlignment="Top" Width="75"
                Click="ButtonSync_Click"/>
        <Button Content="Async" HorizontalAlignment="Left" Margin="165,190,0,0" VerticalAlignment="Top" Width="75" Click="ButtonAsync_Click"/>
        <Button Content="Async OK" HorizontalAlignment="Left" Margin="285,190,0,0" VerticalAlignment="Top" Width="75" Click="ButtonAsyncOk_Click"/>
    </Grid>
</Window>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp1
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void ButtonSync_Click(object sender, RoutedEventArgs e)
        {
            ContentTextBlock.Text = string.Empty;
            try
            {
                string result = TaskMethod().Result;
                ContentTextBlock.Text = result;
            }
            catch(Exception ex)
            {
                ContentTextBlock.Text = ex.InnerException.Message;
            }
        }

        private void ButtonAsync_Click(object sender, RoutedEventArgs e)
        {
            ContentTextBlock.Text = string.Empty;
            Mouse.OverrideCursor = Cursors.Wait;
            Task<string> task = TaskMethod();
            task.ContinueWith(t =>
            {
                ContentTextBlock.Text = t.Exception.InnerException.Message;
                Mouse.OverrideCursor = null;
            },CancellationToken.None,TaskContinuationOptions.OnlyOnFaulted,TaskScheduler.FromCurrentSynchronizationContext());
        }

        private void ButtonAsyncOk_Click(object sender, RoutedEventArgs e)
        {
            ContentTextBlock.Text = string.Empty;
            Mouse.OverrideCursor = Cursors.Wait;
            Task<string> task = TaskMethod(TaskScheduler.FromCurrentSynchronizationContext());
            task.ContinueWith(t => Mouse.OverrideCursor = null, CancellationToken.None, TaskContinuationOptions.None,
                TaskScheduler.FromCurrentSynchronizationContext());
        }
        Task<string> TaskMethod()
        {
            return TaskMethod(TaskScheduler.Default);
        }
        Task<string> TaskMethod(TaskScheduler scheduler)
        {
            Task delay = Task.Delay(TimeSpan.FromSeconds(2));
            return delay.ContinueWith(t=> 
            {
                string str = $"Task is running on a thread id " +
                $"{Thread.CurrentThread.ManagedThreadId}. Is thread pool thread:" +
                $"{Thread.CurrentThread.IsThreadPoolThread}";
                ContentTextBlock.Text = str;
                return str;
                
            },scheduler);
        }
    }
}

posted @ 2022-02-16 23:53  JohnYang819  阅读(445)  评论(0编辑  收藏  举报