.NET 4中的多线程编程之一:使用Task

.NET 4为了简化多线程编程,提供了System.Threading.Tasks命名空间中的类来帮助开发者进行多线程编程,其中,Task类用来表示一个线程。最简单的Task类接受一个Action委托作为要执行的方法,调用Start方法开始在另一个线程中运行。例如:

using System;
using System.Threading.Tasks;
namespace TaskParallel
{
    class Program
    {
        static void Main(string[] args)
        {
            Task task=new Task(()=> Console.WriteLine("Write from another thread"));
            task.Start(); 
            Console.WriteLine("Main Thread");
            Console.ReadLine();         
        }
    }
}

两句输出的顺序是不一定的,但是很有可能是:

Main Thread
Write from another thread

也可以使用Task.Factory.StartNew方法,这个方法会构造一个Task并且立刻开始运行,相当于将Task的构造函数和Start方法连在一起执行。

Task类还有一个构造函数可以接受Action<object>委托,用来向Action委托传递参数:

static void Main(string[] args)
{
    for (int i = 0; i < 5; i++)
    {
        Task t = new Task(obj => Console.WriteLine("Thread No " + obj), i);
        t.Start();
    }
    Console.ReadLine();   
}

输出的结果类似于:

Thread No 2
Thread No 1
Thread No 3
Thread No 0
Thread No 4


可以使用Task<T>类来获得返回值,T是返回值的类型,例如:

static void Main(string[] args)
{
     Task<int> t = new Task<int>(() =>
     {
         int s=0;
         for (int i = 0; i < 10000; i++)
             s += i;
         return s;
      });
      t.Start();
      Console.WriteLine("I'm computing");
      Console.WriteLine(t.Result);
      Console.ReadLine();
}
结果为:

I'm computing
49995000

在访问t.Result的时候,.net 会保证此时Task的代码已经执行完毕,Result已经获得,否则该线程会阻塞,直到Result计算完毕。

 

Task库提供了一种主动终止线程的方法,先创建一个CancellationTokenSource,将其Token属性通过Task构造函数传进去,在Task内部轮询token的IsCancellationReqeusted属性,如果检测到为true,则主动终止线程。在父线程内调用tokenSource的Cancel方法,可以终止线程。注意,这是线程主动终止自己的方法,必须在Task内的代码自己终止,.NET不会强行终止task线程,即使父线程调用了tokenSource的Cancel方法。

例如下面的代码,如果在else语句块内没有break语句,子线程是不会终止的。

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

namespace TaskParallel
{
    class Program
    {
        static void Main(string[] args)
        {
            CancellationTokenSource tks = new CancellationTokenSource();
            CancellationToken token = tks.Token;
            long i = 0;
            Task task = new Task(() =>
            {               
                while (true)
                {
                    if (!token.IsCancellationRequested)
                        i++;
                    else
                    {
                        Console.WriteLine("Task is canceled, it looped "+i+" times");
                        break;
                    }
                }
            },token);
            task.Start();
            Console.WriteLine("Press Enter to Cancel task");
            Console.ReadLine();
            tks.Cancel();
            Console.ReadLine();
        }
    }
}

还可以通过CancellationToken的Register方法给token注册一个委托,当调用tokenSource的Cancel方法的时候,这个委托会被执行。这个委托是在调用Cancel方法的线程中执行的。

token.Register(() => { Console.WriteLine("Delegate Invoked"); });
 
要挂起当前线程,等待一个线程执行完成,可以使用执行线程的Wait()方法,Wait方法有一些重载方法,可以指定等待的时间等,例如:
static void Main(string[] args)
{
    Task t1 = new Task(() =>
    {
        Console.WriteLine("Task 1 Starts...");
        Thread.Sleep(3000);
        Console.WriteLine("Task1 Ends");
    });
    t1.Start();
    Task t2 = new Task(() =>
    {
        Console.WriteLine("Task2 Starts...");
        t1.Wait();
        Console.WriteLine("Task2 Ends");
    });
    Task t3 = new Task(() =>
        {
            Console.WriteLine("Task 3 is waiting Task1 for 1 second...");
            bool finished=t1.Wait(1000);
            if (finished)
                Console.WriteLine("T1 is finished");
            else
                Console.WriteLine("T1 is not finished");                   
        });
    t2.Start();
    t3.Start();
    Console.ReadLine();
}

执行结果为:

Task 1 Starts...
Task2 Starts...
Task 3 is waiting Task1 for 1 second...
T1 is not finished
Task1 Ends
Task2 Ends

要等到多个线程都执行完可以使用Task.WaitAll方法,

Task t4 = new Task(() =>
{
    Console.WriteLine("Waiting for all");
    Task.WaitAll(t1, t2, t3);
    Console.WriteLine("Task4 Ends");
});
t4.Start();

还有一个Task.WaitAny,可以等待一组线程中的任何一个方法执行完毕,用法类似。

 

下面介绍Task中的异常处理,通常情况下,线程委托中的异常会导致线程终止,但异常并不会被抛出,例如:

Task t = new Task(() =>
{
    throw new Exception();
    Console.WriteLine("Thread Ends");
});
t.Start();
Console.WriteLine("Main Ends");

输出的是:

Main Ends

当调用Wait,WaitAll,WaitAny,Task.Result的时候,会抛出AggerateException ,在AggerateExcepiton中可以处理所有线程抛出的异常:

static void Main(string[] args)
       {
           Task t1 = new Task(() =>
           {
               throw new Exception();
               Console.WriteLine("T1 Ends");
           });

           Task t2 = new Task(() =>
           {
               throw new ArgumentException();
               Console.WriteLine("T2 Ends");
           });
           t1.Start();
           t2.Start(); 
           try
           {
               Task.WaitAll(t1, t2);
           }
           catch (AggregateException ex)
           {
               foreach (var inner in ex.InnerExceptions)
               {
                   Console.WriteLine(inner.GetType() + " " + inner.Source);
               }
           }
           Console.WriteLine("Main Ends");
       }
   }

有时候需要区分对待某些异常,一些异常直接处理掉,一些异常需要再次抛出,AggregateException提供一个Handle方法,接收一个Func<Exception,bool>委托作为参数,如果不需要再次抛出则返回true,否则返回false。

try
{
       Task.WaitAll(t1, t2);
}
catch (AggregateException ex)
{
        foreach (var inner in ex.InnerExceptions)
        {
             Console.WriteLine(inner.GetType() + " " + inner.Source);
        }
        ex.Handle((e) =>
        {
              if (e is ArgumentException)
              {
                   Console.WriteLine("Argument Exception is captured");
                   return true;
              }
              else
                   return false;
         });
}
这样就会有未处理的异常被抛出,抛出的仍然是AggregateException.
 

posted @ 2011-11-04 02:17  yinzixin  阅读(5782)  评论(6编辑  收藏  举报