您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
C#综合揭秘——细说多线程(下)
 
作者 风尘浪子的博客,火龙果软件    发布于 2014-08-04
   次浏览      
 

七、并行编程与PLINQ

要使用多线程开发,必须非常熟悉Thread的使用,而且在开发过程中可能会面对很多未知的问题。为了简化开发,.NET 4.0 特别提供一个并行编程库System.Threading.Tasks,它可以简化并行开发,你无需直接跟线程或线程池打交道,就可以简单建立多线程应用程序。此外,.NET还提供了新的一组扩展方法PLINQ,它具有自动分析查询功能,如果并行查询能提高系统效率,则同时运行,如果查询未能从并行查询中受益,则按原顺序查询。下面将详细介绍并行操作的方式。

7.1 泛型委托

使用并行编程可以同时操作多个委托,在介绍并行编程前先简单介绍一下两个泛型委托System.Func<>与System.Action<>。

Func<>是一个能接受多个参数和一个返回值的泛型委托,它能接受0个到16个输入参数, 其中 T1,T2,T3,T4......T16 代表自定的输入类型,TResult为自定义的返回值。

public delegate TResult Func<TResult>()
public delegate TResult Func<T1,TResult>(T1 arg1)
public delegate TResult Func<T1,T2, TResult>(T1 arg1,T2 arg2)
public delegate TResult Func<T1,T2, T3, TResult>(T1 arg1,T2 arg2,T3 arg3)
public delegate TResult Func<T1,T2, T3, ,T4, TResult>(T1 arg1,T2 arg2,T3 arg3,T4 arg4)
..............
public delegate TResult Func<T1,T2, T3, ,T4, ...... ,T16,TResult>(T1 arg1,T2 arg2,T3 arg3,T4 arg4,...... ,T16 arg16)

Action<>与Func<>十分相似,不同在于Action<>的返回值为void,Action能接受0~16个参数

public delegate void Action<T1>()
public delegate void Action<T1,T2>(T1 arg1,T2 arg2)
public delegate void Action<T1,T2, T3>(T1 arg1,T2 arg2, T3 arg3)
.............
public delegate void Action<T1,T2, T3, ,T4, ...... ,T16>(T1 arg1,T2 arg2,T3 arg3,T4 arg4,...... ,T16 arg16)

7.2 任务并行库(TPL)

System.Threading.Tasks中的类被统称为任务并行库(Task Parallel Library,TPL),TPL使用CLR线程池把工作分配到CPU,并能自动处理工作分区、线程调度、取消支持、状态管理以及其他低级别的细节操作,极大地简化了多线程的开发。

注意:TPL比Thread更具智能性,当它判断任务集并没有从并行运行中受益,就会选择按顺序运行。但并非所有的项目都适合使用并行开发,创建过多并行任务可能会损害程序的性能,降低运行效率。

TPL包括常用的数据并行与任务并行两种执行方式:

7.2.1 数据并行

数据并行的核心类就是System.Threading.Tasks.Parallel,它包含两个静态方法 Parallel.For 与 Parallel.ForEach, 使用方式与for、foreach相仿。通过这两个方法可以并行处理System.Func<>、System.Action<>委托。

以下一个例子就是利用 public static ParallelLoopResult For( int from, int max, Action<int>) 方法对List<Person>进行并行查询。

假设使用单线程方式查询3个Person对象,需要用时大约6秒,在使用并行方式,只需使用2秒就能完成查询,而且能够避开Thread的繁琐处理。

class Program
{
static void Main(string[] args)
{
//设置最大线程数
ThreadPool.SetMaxThreads(1000, 1000);
//并行查询
Parallel.For(0, 3,n =>
{
Thread.Sleep(2000); //模拟查询
ThreadPoolMessage(GetPersonList()[n]);
});
Console.ReadKey();
}

//模拟源数据
static IList<Person> GetPersonList()
{
var personList = new List<Person>();

var person1 = new Person();
person1.ID = 1;
person1.Name = "Leslie";
person1.Age = 30;
personList.Add(person1);
...........
return personList;
}

//显示线程池现状
static void ThreadPoolMessage(Person person)
{
int a, b;
ThreadPool.GetAvailableThreads(out a, out b);
string message = string.Format("Person ID:{0} Name:{1} Age:{2}\n" +
" CurrentThreadId is {3}\n WorkerThreads is:{4}" +
" CompletionPortThreads is :{5}\n",
person.ID, person.Name, person.Age,
Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString());

Console.WriteLine(message);
}
}

观察运行结果,对象并非按照原排列顺序进行查询,而是使用并行方式查询。

若想停止操作,可以利用ParallelLoopState参数,下面以ForEach作为例子。

public static ParallelLoopResult ForEach<TSource>
( IEnumerable<TSource> source, Action<TSource,  ParallelLoopState> action)

其中source为数据集,在Action<TSource,ParallelLoopState>委托的ParallelLoopState参数当中包含有Break()和 Stop()两个方法都可以使迭代停止。Break的使用跟传统for里面的使用方式相似,但因为处于并行处理当中,使用Break并不能保证所有运行能立即停止,在当前迭代之前的迭代会继续执行。若想立即停止操作,可以使用Stop方法,它能保证立即终止所有的操作,无论它们是处于当前迭代的之前还是之后。

class Program
{
static void Main(string[] args)
{
//设置最大线程数
ThreadPool.SetMaxThreads(1000, 1000);

//并行查询
Parallel.ForEach(GetPersonList(), (person, state) =>
{
if (person.ID == 2)
state.Stop();
ThreadPoolMessage(person);
});
Console.ReadKey();
}

//模拟源数据
static IList<Person> GetPersonList()
{
var personList = new List<Person>();

var person1 = new Person();
person1.ID = 1;
person1.Name = "Leslie";
person1.Age = 30;
personList.Add(person1);
..........
return personList;
}

//显示线程池现状
static void ThreadPoolMessage(Person person)
{
int a, b;
ThreadPool.GetAvailableThreads(out a, out b);
string message = string.Format("Person ID:{0} Name:{1} Age:{2}\n" +
" CurrentThreadId is {3}\n WorkerThreads is:{4}" +
" CompletionPortThreads is :{5}\n",
person.ID, person.Name, person.Age,
Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString());

Console.WriteLine(message);
}
}

观察运行结果,当Person的ID等于2时,运行将会停止。

当要在多个线程中调用本地变量,可以使用以下方法:

public static ParallelLoopResult ForEach<TSource, 
                          TLocal>(IEnumerable<Of TSource>, Func<Of 
                          TLocal>, Func<Of TSource,ParallelLoopState,TLocal,TLocal>, 
                          Action<Of TLocal>)

其中第一个参数为数据集;

第二个参数是一个Func委托,用于在每个线程执行前进行初始化;

第三个参数是委托Func<Of T1,T2,T3,TResult>,它能对数据集的每个成员进行迭代,当中T1是数据集的成员,T2是一个ParallelLoopState对 象,它可以控制迭代的状态,T3是线程中的本地变量;

第四个参数是一个Action委托,用于对每个线程的最终状态进行最终操作。

在以下例子中,使用ForEach计算多个Order的总体价格。在ForEach方法中,首先把参数初始化为0f,然后用把同一个Order的多个OrderItem价格进行累加,计算出Order的价格,最后把多个Order的价格进行累加,计算出多个Order的总体价格。

public class Order
{
public int ID;
public float Price;
}

public class OrderItem
{
public int ID;
public string Goods;
public int OrderID;
public float Price;
public int Count;
}

class Program
{
static void Main(string[] args)
{
//设置最大线程数
ThreadPool.SetMaxThreads(1000, 1000);
float totalPrice = 0f;
//并行查询
var parallelResult = Parallel.ForEach(GetOrderList(),
() => 0f, //把参数初始值设为0
(order, state, orderPrice) =>
{
//计算单个Order的价格
orderPrice = GetOrderItem().Where(item => item.OrderID == order.ID)
.Sum(item => item.Price * item.Count);
order.Price = orderPrice;
ThreadPoolMessage(order);

return orderPrice;
},
(finallyPrice) =>
{
totalPrice += finallyPrice;//计算多个Order的总体价格
}
);

while (!parallelResult.IsCompleted)
Console.WriteLine("Doing Work!");

Console.WriteLine("Total Price is:" + totalPrice);
Console.ReadKey();
}
//虚拟数据
static IList<Order> GetOrderList()
{
IList<Order> orderList = new List<Order>();
Order order1 = new Order();
order1.ID = 1;
orderList.Add(order1);
............
return orderList;
}
//虚拟数据
static IList<OrderItem> GetOrderItem()
{
IList<OrderItem> itemList = new List<OrderItem>();

OrderItem orderItem1 = new OrderItem();
orderItem1.ID = 1;
orderItem1.Goods = "iPhone 4S";
orderItem1.Price = 6700;
orderItem1.Count = 2;
orderItem1.OrderID = 1;
itemList.Add(orderItem1);
...........
return itemList;
}

//显示线程池现状
static void ThreadPoolMessage(Order order)
{
int a, b;
ThreadPool.GetAvailableThreads(out a, out b);
string message = string.Format("OrderID:{0} OrderPrice:{1}\n" +
" CurrentThreadId is {2}\n WorkerThreads is:{3}" +
" CompletionPortThreads is:{4}\n",
order.ID, order.Price,
Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString());

Console.WriteLine(message);
}
}

运行结果

7.2.2 任务并行

在TPL当中还可以使用Parallel.Invoke方法触发多个异步任务,其中 actions 中可以包含多个方法或者委托,parallelOptions用于配置Parallel类的操作。

public static void Invoke(Action[] actions )
public static void Invoke(ParallelOptions parallelOptions, Action[] actions )

下面例子中利用了Parallet.Invoke并行查询多个Person,actions当中可以绑定方法、lambda表达式或者委托,注意绑定方法时必须是返回值为void的无参数方法。

class Program
{
static void Main(string[] args)
{
//设置最大线程数
ThreadPool.SetMaxThreads(1000, 1000);

//任务并行
Parallel.Invoke(option,
PersonMessage,
()=>ThreadPoolMessage(GetPersonList()[1]),
delegate(){
ThreadPoolMessage(GetPersonList()[2]);
});
Console.ReadKey();
}

static void PersonMessage()
{
ThreadPoolMessage(GetPersonList()[0]);
}

//显示线程池现状
static void ThreadPoolMessage(Person person)
{
int a, b;
ThreadPool.GetAvailableThreads(out a, out b);
string message = string.Format("Person ID:{0} Name:{1} Age:{2}\n" +
" CurrentThreadId is {3}\n WorkerThreads is:{4}" +
" CompletionPortThreads is :{5}\n",
person.ID, person.Name, person.Age,
Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString());

Console.WriteLine(message);
}

//模拟源数据
static IList<Person> GetPersonList()
{
var personList = new List<Person>();

var person1 = new Person();
person1.ID = 1;
person1.Name = "Leslie";
person1.Age = 30;
personList.Add(person1);
..........
return personList;
}
}

运行结果

7.3 Task简介

以Thread创建的线程被默认为前台线程,当然你可以把线程IsBackground属性设置为true,但TPL为此提供了一个更简单的类Task。
Task存在于System.Threading.Tasks命名空间当中,它可以作为异步委托的简单替代品。

通过Task的Factory属性将返回TaskFactory类,以TaskFactory.StartNew(Action)方法可以创建一个新线程,所创建的线程默认为后台线程。

class Program
{
static void Main(string[] args)
{
ThreadPool.SetMaxThreads(1000, 1000);
Task.Factory.StartNew(() => ThreadPoolMessage());
Console.ReadKey();
}

//显示线程池现状
static void ThreadPoolMessage()
{
int a, b;
ThreadPool.GetAvailableThreads(out a, out b);
string message = string.Format("CurrentThreadId is:{0}\n" +
"CurrentThread IsBackground:{1}\n" +
"WorkerThreads is:{2}\nCompletionPortThreads is:{3}\n",
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsBackground.ToString(),
a.ToString(), b.ToString());
Console.WriteLine(message);
}
}

运行结果

若要取消处理,可以利用CancellationTakenSource对象,在TaskFactory中包含有方法

public Task StartNew( Action action, CancellationToken cancellationToken )

在方法中加入CancellationTakenSource对象的CancellationToken属性,可以控制任务的运行,调用CancellationTakenSource.Cancel时任务就会自动停止。下面以图片下载为例子介绍一下TaskFactory的使用。

服务器端页面

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<script type="text/C#" runat="server">
private static List<string> url=new List<string>();

protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
url.Clear();
Application["Url"] = null;
}
}

protected void CheckBox_CheckedChanged(object sender, EventArgs e)
{
CheckBox checkBox = (CheckBox)sender;
if (checkBox.Checked)
url.Add(checkBox.Text);
else
url.Remove(checkBox.Text);
Application["Url"]= url;
}
</script>
</head>
<body>
<form id="form1" runat="server" >
<div align="left">
<div align="center" style="float: left;">
<asp:Image ID="Image1" runat="server" ImageUrl="~/Images/A.jpg" /><br />
<asp:CheckBox ID="CheckBox1" runat="server" AutoPostBack="True"
oncheckedchanged="CheckBox_CheckedChanged" Text="A.jpg" />
</div>
<div align="center" style="float: left">
<asp:Image ID="Image2" runat="server" ImageUrl="~/Images/B.jpg" /><br />
<asp:CheckBox ID="CheckBox2" runat="server" AutoPostBack="True"
oncheckedchanged="CheckBox_CheckedChanged" Text="B.jpg" />
</div>
<div align="center" style="float: left">
<asp:Image ID="Image3" runat="server" ImageUrl="~/Images/C.jpg" /><br />
<asp:CheckBox ID="CheckBox3" runat="server" AutoPostBack="True"
oncheckedchanged="CheckBox_CheckedChanged" Text="C.jpg" />
</div>
<div align="center" style="float: left">
<asp:Image ID="Image4" runat="server" ImageUrl="~/Images/D.jpg" /><br />
<asp:CheckBox ID="CheckBox4" runat="server" AutoPostBack="True"
oncheckedchanged="CheckBox_CheckedChanged" Text="D.jpg" />
</div>
<div align="center" style="float: left">
<asp:Image ID="Image5" runat="server" ImageUrl="~/Images/E.jpg" /><br />
<asp:CheckBox ID="CheckBox5" runat="server" AutoPostBack="True"
oncheckedchanged="CheckBox_CheckedChanged" Text="E.jpg" />
</div>
</div>
</form>
</body>
</html>

首先在服务器页面中显示多个*.jpg图片,每个图片都有对应的CheckBox检测其选择情况。
所选择图片的路径会记录在Application["Url"]当中传递到Handler.ashx当中。

注意:Application是一个全局变量,此处只是为了显示Task的使用方式,在ASP.NET开发应该慎用Application。

Handler.ashx 处理图片的下载,它从 Application["Url"] 当中获取所选择图片的路径,并把图片转化成byte[]二进制数据。

再把图片的数量,每副图片的二进制数据的长度记录在OutputStream的头部。

最后把图片的二进制数据记入 OutputStream 一并输出。

public class Handler : IHttpHandler 
{
public void ProcessRequest(HttpContext context)
{
//获取图片名,把图片数量写OutputStream
List<String> urlList = (List<string>)context.Application["Url"];
context.Response.OutputStream.Write(BitConverter.GetBytes(urlList.Count), 0, 4);

//把图片转换成二进制数据
List<string> imageList = GetImages(urlList);

//把每副图片长度写入OutputStream
foreach (string image in imageList)
{
byte[] imageByte=Convert.FromBase64String(image);
context.Response.OutputStream.Write(BitConverter.GetBytes(imageByte.Length),0,4);
}

//把图片写入OutputStream
foreach (string image in imageList)
{
byte[] imageByte = Convert.FromBase64String(image);
context.Response.OutputStream.Write(imageByte,0,imageByte.Length);
}
}

//获取多个图片的二进制数据
private List<string> GetImages(List<string> urlList)
{
List<string> imageList = new List<string>();
foreach (string url in urlList)
imageList.Add(GetImage(url));
return imageList;
}

//获取单副图片的二进制数据
private string GetImage(string url)
{
string path = "E:/My Projects/Example/WebSite/Images/"+url;
FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read);
byte[] imgBytes = new byte[10240];
int imgLength = stream.Read(imgBytes, 0, 10240);
return Convert.ToBase64String(imgBytes,0,imgLength);
}

public bool IsReusable
{
get{ return false;}
}
}

客户端

建立一个WinForm窗口,里面加入一个WebBrowser连接到服务器端的Default.aspx页面。

当按下Download按键时,系统就会利用TaskFactory.StartNew的方法建立异步线程,使用WebRequest方法向Handler.ashx发送请求。

接收到回传流时,就会根据头文件的内容判断图片的数量与每副图片的长度,把二进制数据转化为*.jpg文件保存。

系统利用TaskFactory.StartNew(action,cancellationToken) 方式异步调用GetImages方法进行图片下载。

当用户按下Cancel按钮时,异步任务就会停止。值得注意的是,在图片下载时调用了CancellationToken.ThrowIfCancellationRequested方法,目的在检查并行任务的运行情况,在并行任务被停止时释放出OperationCanceledException异常,确保用户按下Cancel按钮时,停止所有并行任务。

public partial class Form1 : Form
{
private CancellationTokenSource tokenSource = new CancellationTokenSource();

public Form1()
{
InitializeComponent();
ThreadPool.SetMaxThreads(1000, 1000);
}

private void downloadToolStripMenuItem_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(GetImages,tokenSource.Token);
}

private void cancelToolStripMenuItem_Click(object sender, EventArgs e)
{
tokenSource.Cancel();
}

private void GetImages()
{
//发送请求,获取输出流
WebRequest webRequest = HttpWebRequest.Create("Http://localhost:5800/Handler.ashx");
Stream responseStream=webRequest.GetResponse().GetResponseStream();

byte[] responseByte = new byte[81960];
IAsyncResult result=responseStream.BeginRead(responseByte,0,81960,null,null);
int responseLength = responseStream.EndRead(result);

//获取图片数量
int imageCount = BitConverter.ToInt32(responseByte, 0);

//获取每副图片的长度
int[] lengths = new int[imageCount];
for (int n = 0; n < imageCount; n++)
{
int length = BitConverter.ToInt32(responseByte, (n + 1) * 4);
lengths[n] = length;
}
try
{
//保存图片
for (int n = 0; n < imageCount; n++)
{
string path = string.Format("E:/My Projects/Example/Test/Images/pic{0}.jpg", n);
FileStream file = new FileStream(path, FileMode.Create, FileAccess.ReadWrite);

//计算字节偏移量
int offset = (imageCount + 1) * 4;
for (int a = 0; a < n; a++)
offset += lengths[a];

file.Write(responseByte, offset, lengths[n]);
file.Flush();

//模拟操作
Thread.Sleep(1000);

//检测CancellationToken变化
tokenSource.Token.ThrowIfCancellationRequested();
}
}
catch (OperationCanceledException ex)
{
MessageBox.Show("Download cancel!");
}
}
}

7.4 并行查询(PLINQ)

并行 LINQ (PLINQ) 是 LINQ 模式的并行实现,主要区别在于 PLINQ 尝试充分利用系统中的所有处理器。 它利用所有处理器的方法,把数据源分成片段,然后在多个处理器上对单独工作线程上的每个片段并行执行查询, 在许多情况下,并行执行意味着查询运行速度显著提高。但这并不说明所有PLINQ都会使用并行方式,当系统测试要并行查询会对系统性能造成损害时,那将自动化地使用同步执行。

在System.Linq.ParallelEnumerable类中,包含了并行查询的大部分方法。

 

7.4.1 AsParallel

通常想要实现并行查询,只需向数据源添加 AsParallel 查询操作即可。

class Program
{
static void Main(string[] args)
{
var personList=GetPersonList().AsParallel()
.Where(x=>x.Age>30);
Console.ReadKey();
}

//模拟源数据
static IList<Person> GetPersonList()
{
var personList = new List<Person>();

var person1 = new Person();
person1.ID = 1;
person1.Name = "Leslie";
person1.Age = 30;
personList.Add(person1);
...........
return personList;
}
}

7.4.2 AsOrdered

若要使查询结果必须保留源序列排序方式,可以使用AsOrdered方法。

AsOrdered依然使用并行方式,只是在查询过程加入额外信息,在并行结束后把查询结果再次进行排列。

class Program
{
static void Main(string[] args)
{
var personList=GetPersonList().AsParallel().AsOrdered()
.Where(x=>x.Age<30);
Console.ReadKey();
}

static IList<Person> GetPersonList()
{......}
}

7.4.3 WithDegreeOfParallelism

默认情况下,PLINQ 使用主机上的所有处理器,这些处理器的数量最多可达 64 个。

通过使用 WithDegreeOfParallelism(Of TSource) 方法,可以指示 PLINQ 使用不多于指定数量的处理器。

class Program
{
static void Main(string[] args)
{
var personList=GetPersonList().AsParallel().WithDegreeOfParallelism(2)
.Where(x=>x.Age<30);
Console.ReadKey();
}

static IList<Person> GetPersonList()
{.........}
}

7.4.4 ForAll

如果要对并行查询结果进行操作,一般会在for或foreach中执行,执行枚举操作时会使用同步方式。

有见及此,PLINQ中包含了ForAll方法,它可以使用并行方式对数据集进行操作。

class Program
{
static void Main(string[] args)
{
ThreadPool.SetMaxThreads(1000, 1000);
GetPersonList().AsParallel().ForAll(person =>{
ThreadPoolMessage(person);
});
Console.ReadKey();
}

static IList<Person> GetPersonList()
{.......}

//显示线程池现状
static void ThreadPoolMessage(Person person)
{
int a, b;
ThreadPool.GetAvailableThreads(out a, out b);
string message = string.Format("Person ID:{0} Name:{1} Age:{2}\n" +
" CurrentThreadId is {3}\n WorkerThreads is:{4}" +
" CompletionPortThreads is :{5}\n",
person.ID, person.Name, person.Age,
Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString());
Console.WriteLine(message);
}
}

运行结果

7.4.5 WithCancellation

如果需要停止查询,可以使用 WithCancellation(Of TSource) 运算符并提供 CancellationToken 实例作为参数。

与第三节Task的例子相似,如果标记上的 IsCancellationRequested 属性设置为 true,则 PLINQ 将会注意到它,并停止所有线程上的处理,然后引发 OperationCanceledException。这可以保证并行查询能够立即停止。

class Program
{
static CancellationTokenSource tokenSource = new CancellationTokenSource();

static void Main(string[] args)
{
Task.Factory.StartNew(Cancel);
try
{
GetPersonList().AsParallel().WithCancellation(tokenSource.Token)
.ForAll(person =>
{
ThreadPoolMessage(person);
});
}
catch (OperationCanceledException ex)
{ }
Console.ReadKey();
}

//在10~50毫秒内发出停止信号
static void Cancel()
{
Random random = new Random();
Thread.Sleep(random.Next(10,50));
tokenSource.Cancel();
}

static IList<Person> GetPersonList()
{......}

//显示线程池现状
static void ThreadPoolMessage(Person person)
{
int a, b;
ThreadPool.GetAvailableThreads(out a, out b);
string message = string.Format("Person ID:{0} Name:{1} Age:{2}\n" +
" CurrentThreadId is {3}\n WorkerThreads is:{4}" +
" CompletionPortThreads is :{5}\n",
person.ID, person.Name, person.Age,
Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString());
Console.WriteLine(message);
}
}

 

八、定时器与锁

8.1定时器

若要长期定时进行一些工作,比如像邮箱更新,实时收听信息等等,可以利用定时器Timer进行操作。

在System.Threading命名空间中存在Timer类与对应的TimerCallback委托,它可以在后台线程中执行一些长期的定时操作,使主线程不受干扰。

Timer类中最常用的构造函数为 public Timer( timerCallback , object , int , int )

timerCallback委托可以绑定执行方法,执行方法必须返回void,它可以是无参数方法,也可以带一个object参数的方法。

第二个参数是为 timerCallback 委托输入的参数对象。

第三个参数是开始执行前等待的时间。

第四个参数是每次执行之间的等待时间。

开发实例

class Program
{
static void Main(string[] args)
{
ThreadPool.SetMaxThreads(1000, 1000);

TimerCallback callback = new TimerCallback(ThreadPoolMessage);
Timer t = new Timer(callback,"Hello Jack! ", 0, 1000);
Console.ReadKey();
}

//显示线程池现状
static void ThreadPoolMessage(object data)
{
int a, b;
ThreadPool.GetAvailableThreads(out a, out b);
string message = string.Format("{0}\n CurrentThreadId is:{1}\n" +
" CurrentThread IsBackground:{2}\n" +
" WorkerThreads is:{3}\n CompletionPortThreads is:{4}\n",
data + "Time now is " + DateTime.Now.ToLongTimeString(),
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsBackground.ToString(),
a.ToString(), b.ToString());
Console.WriteLine(message);
}
}

注意观察运行结果,每次调用Timer绑定的方法时不一定是使用同一线程,但线程都会是来自工作者线程的后台线程。

8.2 锁

在使用多线程开发时,存在一定的共用数据,为了避免多线程同时操作同一数据,.NET提供了lock、Monitor、Interlocked等多个锁定数据的方式。

8.2.1 lock

lock的使用比较简单,如果需要锁定某个对象时,可以直接使用lock(this)的方式。

private void Method()
{
lock(this)
{
//在此进行的操作能保证在同一时间内只有一个线程对此对象操作
}
}

如果操作只锁定某段代码,可以事先建立一个object对象,并对此对象进行操作锁定,这也是.net提倡的锁定用法。

class Control
{
private object obj=new object();

public void Method()
{
lock(obj)
{.......}
}
}

8.2.2 Montior

Montior存在于System.Thread命名空间内,相比lock,Montior使用更灵活。

它存在 Enter, Exit 两个方法,它可以对对象进行锁定与解锁,比lock使用更灵活。

class Control
{
private object obj=new object();

public void Method()
{
Monitor.Enter(obj);
try
{......}
catch(Excetion ex)
{......}
finally
{
Monitor.Exit(obj);
}
}
}

使用try的方式,能确保程序不会因死锁而释放出异常!

而且在finally中释放obj对象能够确保无论是否出现死锁状态,系统都会释放obj对象。

而且Monitor中还存在Wait方法可以让线程等待一段时间,然后在完成时使用Pulse、PulseAll等方法通知等待线程。

8.2.3 Interlocked

Interlocked存在于System.Thread命名空间内,它的操作比Monitor使用更简单。

它存在CompareExchange、Decrement、Exchange、Increment等常用方法让参数在安全的情况进行数据交换。

Increment、Decrement 可以使参数安全地加1或减1并返回递增后的新值。

class Example
{
private int a=1;

public void AddOne()
{
int newA=Interlocked.Increment(ref a);
}
}

Exchange可以安全地变量赋值。

public void SetData()
2 {
3 Interlocked.Exchange(ref a,100);
4 }

CompareExchange使用特别方便,它相当于if的用法,当a等于1时,则把100赋值给a。

public void CompareAndExchange()
2 {
3 Interlocked.CompareExchange(ref a,100,1);
4 }

结束语

熟悉掌握多线程开发,对提高系统工作效率非常有帮助,尤其是回调方法与最近火热的并行编程更应该引起各位的重视。

在下用了较长的时间总结所用过的多线程开发方式,希望本篇文章能对各位的学习研究有所帮助,当中有所错漏的地方敬请点评。

   
次浏览       
 
相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
 
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
 
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]

使用decj简化Web前端开发
Web开发框架形成之旅
更有效率的使用Visual Studio
MVP+WCF+三层结构搭建框架
ASP.NET运行机制浅析【图解】
编写更好的C#代码
10个Visual Studio开发调试技巧
更多...   


.NET框架与分布式应用架构设计
.NET & WPF & WCF应用开发
UML&.Net架构设计
COM组件开发
.Net应用开发
InstallShield


日照港 .NET Framework & WCF应用开发
神华信息 .NET单元测试
北京 .Net应用软件系统架构
台达电子 .NET程序设计与开发
赛门铁克 C#与.NET架构设计
广东核电 .Net应用系统架构
更多...