22 C# 第十八章 TPL 并行编程

C# TPL(Task Parallel Library) 并行编程是.Net4 为多线程编程引入的新的API。因为并行编程涉及到的知识非常广泛,这里只是简单的把.Net 4中TPL的用法总结了一下。

一: Task 线程的基本使用


关于Action委托: 它是 .Net 定义的一种委托类型。
public delegate void Action():  封装一个方法,该方法不具有参数并且不返回值。
public delegate void Action<in T>(T obj):  封装一个方法,该方法只有一个参数并且不返回值。
...  ...


可以使用此委托以参数形式传递方法,而不用显式声明自定义的委托。 封装的方法必须与此委托定义的方法签名相对应。
http://msdn.microsoft.com/zh-cn/library/vstudio/system.action.aspx


光看语言描述理解起来有点费劲。看MSDN给的例子,把自定义的委托和Action比较一下就清楚多了。


两个小例子:

使用委托的例子

using System;
using System.Windows.Forms;


public delegate void ShowValue();


public class Name
{
   private string instanceName;


   public Name(string name)
   {
      this.instanceName = name;
   }


   public void DisplayToConsole()
   {
      Console.WriteLine(this.instanceName);
   }


   public void DisplayToWindow()
   {
      MessageBox.Show(this.instanceName);
   }
}


public class testTestDelegate
{
   public static void Main()
   {
      Name testName = new Name("Koani");
      ShowValue showMethod = testName.DisplayToWindow;
      showMethod();
   }
}




使用 action的例子

using System;
using System.Windows.Forms;


public class Name
{
   private string instanceName;


   public Name(string name)
   {
      this.instanceName = name;
   }


   public void DisplayToConsole()
   {
      Console.WriteLine(this.instanceName);
   }


   public void DisplayToWindow()
   {
      MessageBox.Show(this.instanceName);
   }
}


public class testTestDelegate
{
   public static void Main()
   {
      Name testName = new Name("Koani");
      Action showMethod = testName.DisplayToWindow;
      showMethod();
   }
}

这里把Action 看成了一种特殊的委托,没有参数也没有返回值,这样理解起来就简单了。




关于 Task 的简单介绍
更确切的说它是把线程的概念抽象出来了。这里它更关心的是线程中工作的内容。而把创建线程,重用线程或释放线程等平台相关的工作留给了系统。它更贴近与System.Threading.ThreadPool。一个由系统管理的ThreadPool。


使用 Task 创建线程

 一个简单的线程使用的例子

http://msdn.microsoft.com/zh-cn/library/vstudio/1h2f2459.aspx

using System;
using System.Threading;
using System.Threading.Tasks;

class StartNewDemo
{
    static void Main()
    {
        Action<object> action = (object obj) =>
        {
            Console.WriteLine("Task={0}, obj={1}, Thread={2}", Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);
        };

        // Construct an unstarted task
        Task t1 = new Task(action, "alpha");

        // Cosntruct a started task
        Task t2 = Task.Factory.StartNew(action, "beta");

        // Block the main thread to demonstate that t2 is executing
        t2.Wait();

        // Launch t1 
        t1.Start();

        Console.WriteLine("t1 has been launched. (Main Thread={0})", Thread.CurrentThread.ManagedThreadId);

        // Wait for the task to finish.
        // You may optionally provide a timeout interval or a cancellation token
        // to mitigate situations when the task takes too long to finish.
        t1.Wait();

        // Construct an unstarted task
        Task t3 = new Task(action, "gamma");

        // Run it synchronously
        t3.RunSynchronously();

        // Although the task was run synchrounously, it is a good practice to wait for it which observes for 
        // exceptions potentially thrown by that task.
        t3.Wait();

        Console.ReadKey();
    }
}


程序说明: 


API: Task.Factory

     创建任务并立即启动任务。
     创建在一组任务中的任意或全部任务完成后启动的任务延续项
     创建表示开始/结束方法对的任务,这些方法采用异步编程模型。




Task.RunSynchronously
     任务只可以启动并运行一次。 再次计划任务的尝试将导致异常。通过 RunSynchronously 执行的任务将与当前 TaskScheduler 关联。
     如果目标计划程序不支持当前线程上运行此任务,则在计划程序上计划执行该任务,当前线程会阻塞,直到该任务完成执行。




从线程中返回结果


线程常用的成员
Status: 此任务实例的当前状态 TaskStatus(http://msdn.microsoft.com/zh-cn/library/vstudio/system.threading.tasks.taskstatus.aspx)


IsCompleted: true ,如果任务已完成;否则 false。它不管任务是否出错。


Id: System.Int32类型, 系统分配给此任务实例的一个整数。任务 ID 分配在需要时并不一定表示在任务实例创建的顺序。


AsyncState  它允许跟踪额外的数据。例如,假定多个任务要计算一个List<T>的值。为了将结果放到列表中正确的位置,一个办法是将准备包含结果的那个列表索引存储到AsyncState中。这样一来,在任务结束后,代码可使用AsyncState先转型为int 访问列表中特定索引的位置。这个理解的还不是很透彻,回头应该找个例子看看。

ContinueWith(): 创建一个在目标 Task 完成时异步执行的延续任务。


一个实例
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

// Demonstrates how to associate state with task continuations.
class ContinuationState
{
    // Simluates a lengthy operation and returns the time at which
    // the operation completed.
    public static DateTime DoWork()
    {
        // Simulate work by suspending the current thread 
        // for two seconds.
        Console.WriteLine("Thread = {0} sleep 2 seconds", Task.CurrentId);
        Thread.Sleep(2000);

        // Return the current time.
        return DateTime.Now;
    }

    static void Main(string[] args)
    {
        Action action = () =>
        {
            DoWork();
        };

        Task<DateTime> t = new Task<DateTime>(DoWork);
        t.Start();
        
        Console.WriteLine("Date = {0}", t.Result);
        t.Wait();
        Console.ReadKey();
    }
}



二: Task上的未处理异常

关注点是如何从一个不同的线程中引发的未处理的异常。在Task执行期间产生的未处理的异常会被禁止(suppressed),直到调用某个任务完成(task complete)成员: Wait(), Result, Task.WaitAll(),或者Task.WaitAny()。上述每个成员都会引发任务执行期间发生的任何未处理的异常。


Wait引发异常的例子


using System;
using System.Threading;
using System.Threading.Tasks;

namespace Thread_Task_Sample_Exception_Simple
{
    class Program
    {
        static void Main(string[] args)
        {
            Action<object> action = (object obj) =>
            {
                Console.WriteLine("Task={0}, obj={1}, Thread={2}  Throw Exception", Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);

                throw (new Exception());
            };


            // Cosntruct a started task
            Task t = Task.Factory.StartNew(action, "A");


            for (int i = 0; i < 50; i++)
            {
                Console.Write(".");
                Thread.Sleep(200);
            }
            Console.WriteLine();

            try
            {
                // Block the main thread to demonstate that t2 is executing
                t.Wait();
            }
            catch (Exception ex)
            {
                Console.WriteLine("Task.Wait cause an exception, We caught it");
            }

            Console.ReadKey();
        }
    }
}



使用 ContinueWith ()

using System;
using System.Threading;
using System.Threading.Tasks;

class ContinuationSimpleDemo
{
    // Demonstrated features:
    // 		Task.Factory
    //		Task.ContinueWith()
    //		Task.Wait()
    // Expected results:
    // 		A sequence of three unrelated tasks is created and executed in this order - alpha, beta, gamma.
    //		A sequence of three related tasks is created - each task negates its argument and passes is to the next task: 5, -5, 5 is printed.
    //		A sequence of three unrelated tasks is created where tasks have different types.
    // Documentation:
    static void Main()
    {
        Action<string> action =
            (str) =>
                Console.WriteLine("Task={0}, str={1}, Thread={2}", Task.CurrentId, str, Thread.CurrentThread.ManagedThreadId);

        // Creating a sequence of action tasks (that return no result).
        Console.WriteLine("Creating a sequence of action tasks (that return no result)");
        Task.Factory.StartNew(() => action("alpha"))
            .ContinueWith(antecendent => action("beta"))        // Antecedent data is ignored
            .ContinueWith(antecendent => action("gamma"))
            .Wait();


        Func<int, int> negate =
            (n) =>
            {
                Console.WriteLine("Task={0}, n={1}, -n={2}, Thread={3}", Task.CurrentId, n, -n, Thread.CurrentThread.ManagedThreadId);
                return -n;
            };

        // Creating a sequence of function tasks where each continuation uses the result from its antecendent
        Console.WriteLine("\nCreating a sequence of function tasks where each continuation uses the result from its antecendent");
        Task<int>.Factory.StartNew(() => negate(5))
            .ContinueWith(antecendent => negate(antecendent.Result))		// Antecedent result feeds into continuation
            .ContinueWith(antecendent => negate(antecendent.Result))
            .Wait();


        // Creating a sequence of tasks where you can mix and match the types
        Console.WriteLine("\nCreating a sequence of tasks where you can mix and match the types");
        Task<int>.Factory.StartNew(() => negate(6))
            .ContinueWith(antecendent => action("x"))
            .ContinueWith(antecendent => negate(7))
            .Wait();

        Console.ReadKey();
    }
}

使用ContinueWith 如果在程序中出现异常,后面后续的处理函数可以帮助程序把信息清理干净。



三: 取消任务

野蛮终止的问题:在.Net3.5 或之前的版本,其实并没有怎么支持线程的取消,相反采取的是一种中断的方式。.Net4 中基于PLINQ和TPL的API只支持一种取消请求的方式,协作式取消 -- 目标Task可自行决定是否满足取消请求。


取消任务的一个实例代码:


using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
    public static void Main()
    {
        var tokenSource = new CancellationTokenSource();
        var token = tokenSource.Token;

        // Store references to the tasks so that we can wait on them and   
        // observe their status after cancellation. 
        Task t;
        var tasks = new ConcurrentBag<Task>();

        Console.WriteLine("Press any key to begin tasks...");
        Console.WriteLine("To terminate the example, press 'c' to cancel and exit...");
        Console.ReadKey();
        Console.WriteLine();

        // Request cancellation of a single task when the token source is canceled.  
        // Pass the token to the user delegate, and also to the task so it can   
        // handle the exception correctly.
        t = Task.Factory.StartNew(() => DoSomeWork(1, token), token);
        Console.WriteLine("Task {0} executing", t.Id);
        tasks.Add(t);

        // Request cancellation of a task and its children. Note the token is passed  
        // to (1) the user delegate and (2) as the second argument to StartNew, so   
        // that the task instance can correctly handle the OperationCanceledException.
        t = Task.Factory.StartNew(() =>
        {
            // Create some cancelable child tasks.  
            Task tc;
            for (int i = 3; i <= 10; i++)
            {
                // For each child task, pass the same token  
                // to each user delegate and to StartNew.
                tc = Task.Factory.StartNew(iteration => DoSomeWork((int)iteration, token), i, token);
                Console.WriteLine("Task {0} executing", tc.Id);
                tasks.Add(tc);
                // Pass the same token again to do work on the parent task.   
                // All will be signaled by the call to tokenSource.Cancel below.
                DoSomeWork(2, token);
            }
        }, token);

        Console.WriteLine("Task {0} executing", t.Id);
        tasks.Add(t);

        // Request cancellation from the UI thread.  
        if (Console.ReadKey().KeyChar == 'c')
        {
            tokenSource.Cancel();
            Console.WriteLine("\nTask cancellation requested.");

            // Optional: Observe the change in the Status property on the task.  
            // It is not necessary to wait on tasks that have canceled. However,  
            // if you do wait, you must enclose the call in a try-catch block to  
            // catch the TaskCanceledExceptions that are thrown. If you do   
            // not wait, no exception is thrown if the token that was passed to the   
            // StartNew method is the same token that requested the cancellation. 
        }

        try
        {
            Task.WaitAll(tasks.ToArray());
        }
        catch (AggregateException e)
        {
            Console.WriteLine("\nAggregateException thrown with the following inner exceptions:");
            // Display information about each exception.  
            foreach (var v in e.InnerExceptions)
            {
                if (v is TaskCanceledException)
                    Console.WriteLine("   TaskCanceledException: Task {0}",
                                      ((TaskCanceledException)v).Task.Id);
                else
                    Console.WriteLine("   Exception: {0}", v.GetType().Name);
            }
            Console.WriteLine();
        }

        // Display status of all tasks.  
        foreach (var task in tasks)
            Console.WriteLine("Task {0} status is now {1}", task.Id, task.Status);


        Console.ReadKey();
    }

    static void DoSomeWork(int taskNum, CancellationToken ct)
    {
        // Was cancellation already requested?  
        if (ct.IsCancellationRequested == true)
        {
            Console.WriteLine("Task {0} was cancelled before it got started.", taskNum);
            ct.ThrowIfCancellationRequested();
        }

        int maxIterations = 100;

        // NOTE!!! A "TaskCanceledException was unhandled  
        // by user code" error will be raised here if "Just My Code" 
        // is enabled on your computer. On Express editions JMC is  
        // enabled and cannot be disabled. The exception is benign.  
        // Just press F5 to continue executing your code.  
        for (int i = 0; i <= maxIterations; i++)
        {
            // Do a bit of work. Not too much.  
            var sw = new SpinWait();
            for (int j = 0; j <= 100; j++)
                sw.SpinOnce();

            if (ct.IsCancellationRequested)
            {
                Console.WriteLine("Task {0} cancelled", taskNum);
                ct.ThrowIfCancellationRequested();
            }
        }
    }
}




四: 并行迭代  Task.Parallel


API 会判定同时执行多少个线程效率最高。效率由一个爬山算法来决定。

一个parallel并行的例子

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

namespace Thread_Task_Sample_Parallel
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] data = new int[100];
            int i = 0;

            for (i = 0; i < data.Length; i++)
            {
                data[i] = i;
                Console.Write("{0} ", data[i]);
            }
            Console.WriteLine(" \n ");

            Console.WriteLine("\nParallel running ... ... \n");
            Parallel.For(0, 100, (j)=>
                {
                    System.Threading.Thread.Sleep(1000);
                    data[j] = data[j] * 2;
                    Console.Write("{0} ", data[j]);
                });
            Console.WriteLine("\n\nParallel end ... ... \n");


            Console.WriteLine(" \n ");
            for (i = 0; i < data.Length; i++)
            {
                Console.Write("{0} ", data[i]);
            }
            Console.WriteLine(" \n ");

            Console.ReadKey();
        }
    }
}

测试结果:





实例代码



你可能感兴趣的:(22 C# 第十八章 TPL 并行编程)