Thread
在C#
中,Thread
类位于System.Threading
空间下,当new
一个Thread
实例时,并不会实际创建出一个操作系统层次的线程,只有当调用Start
方法时,才会实际创建出操作系统线程,然后操作系统线程执行C#
线程对应的回调函数。
using System;
using System.Threading;
namespace MultiThread
{
internal class Program
{
public static void Main(string[] args)
{
Console.WriteLine("主线程中开启一个专用线程");
Thread thread = new Thread(ComplexCompute);
thread.Start(6);
Console.WriteLine("主线程做其他的工作");
Thread.Sleep(10000);
Console.WriteLine("请输入Enter结束");
Console.ReadLine();
}
public static void ComplexCompute(object count)
{
int i = 1;
while (i < (int)count)
{
Console.WriteLine("专用线程计时:{0}", i);
Thread.Sleep(1000);
i++;
}
}
}
}
通过观察任务管理器,可以发现,当没有ComplexCompute
这个线程时,一个控制台应用程序会使用6个线程。
当开启ComplexCompute这个线程时,线程数会增加1。
ThreadPool
除了Thread
类外,在CLR
中,也可以使用ThreadPool
来使用一个线程池中的线程,用来处理异步操作,在上面代码中,只需要将
Thread thread = new Thread(ComplexCompute);
thread.Start(6);
替换为
ThreadPool.QueueUserWorkItem(ComplexCompute, 6);
即可。
此时,线程数量为8。
Thread的协作式取消
在主线程中创建一个CancellationTokenSource
实例,将他的CancellationToken
属性作为参数传给专用线程的回调函数,在函数中,判断主线程中是否有取消操作,若有,则返回,从而终止专用线程。
using System;
using System.Threading;
namespace MultiThread
{
internal class Program
{
public static void Main(string[] args)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
Console.WriteLine("主线程中开启一个专用线程");
ThreadPool.QueueUserWorkItem(o => ComplexCompute(cancellationTokenSource, 6));
Console.WriteLine("请输入Enter来终止专用线程");
Console.ReadLine();
cancellationTokenSource.Cancel();
Console.WriteLine("专用线程取消成功");
Console.ReadLine();
}
public static void ComplexCompute(CancellationTokenSource cts, object count)
{
for (int i = 1; i < (int) count; i++)
{
if (cts.IsCancellationRequested)
{
Console.WriteLine("请求取消专用线程");
break;
}
Console.WriteLine("专用线程计时:{0}", i);
Thread.Sleep(1000);
}
}
}
}
Task
此外, 还可以通过Task
类来开启一个线程,与Thread不同的是,只需要将
Thread thread = new Thread(ComplexCompute);
thread.Start(6);
替换为
// 方式一
Task task = new Task(ComplexCompute, 6);
task.Start();
或者
// 方式二
Task.Run(() => ComplexCompute(6));
即可。
此时,线程数量为11。
对于第一种方式,可以给Task
构造器传递一个Action
委托,也可以传递一个Action<Object>
委托,如果是后者,需要向构造器传递实参。如果需要返回值,则可以构造Task<TResult>
对象,例如
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MultiThread
{
internal class Program
{
public static void Main(string[] args)
{
Console.WriteLine("主线程中开启一个专用线程");
Task<int> task = new Task<int>(ComplexCompute, 6);
task.Start();
Console.WriteLine("专用线程计算结果:{0}", task.Result);
Console.WriteLine("主线程做其他的工作");
Thread.Sleep(10000);
Console.WriteLine("请输入Enter结束");
Console.ReadLine();
}
public static int ComplexCompute(object count)
{
int i = 1;
while (i < (int)count)
{
Console.WriteLine("专用线程计时:{0}", i);
Thread.Sleep(1000);
i++;
}
return i;
}
}
}
对于第二种方式,调用Run
时可以传递一个Action
或者Func<TResult>
委托,来执行想要的操作。
对于构造器或者Run
方式,都可以选择性传递一个CancellationToken
,用来协作式取消线程。
Task的协作式取消
与Thread
或者ThreadPool
开启线程不同的是,Task
可以有返回值。在取消一个任务时,为了区别Task.Result
是被取消还是正常返回值,可以使用CancellationToken.ThrowIfCancellationRequested
函数,与CancellationToken.IfCancellationRequested
相同的是会终止Task
,不同的是会抛出一个OperationCanceledException
,从而在使用Task.Result
时会抛出AggregateException。AggregateException
是Task
产生异常时得到的一个集合,可以使用Handle
来处理其中的异常, 例如:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MultiThread
{
internal class Program
{
public static void Main(string[] args)
{
Console.WriteLine("主线程中开启一个专用线程");
CancellationTokenSource cts = new CancellationTokenSource();
Task<int> task = Task.Run(() => ComplexCompute(cts.Token, 6));
Console.WriteLine("请输入Enter取消专用线程");
Console.ReadLine();
cts.Cancel();
try
{
Console.WriteLine("专用线程计算结果:{0}", task.Result);
}
catch (AggregateException e)
{
e.Handle(c => c is OperationCanceledException);
}
Console.WriteLine("请输入Enter结束");
Console.ReadLine();
}
public static int ComplexCompute(CancellationToken token, object count)
{
int i = 1;
while (i < (int)count)
{
token.ThrowIfCancellationRequested();
Console.WriteLine("专用线程计时:{0}", i);
Thread.Sleep(1000);
i++;
}
return i;
}
}
}
问题
- 对于同一个操作,
Thread
、ThreadPool
和Task
实际开启的线程数分别是1、2和5个,不明白其中的原因; - 对于控制台应用程序,其有6个线程,也不知道为什么。