C#:异步编程和线程的使用(.NET 4.5 )
2015-06-09 11:09
288 查看
异步编程和线程处理是并发或并行编程非常重要的功能特征。为了实现异步编程,可使用线程也可以不用。将异步与线程同时讲,将有助于我们更好的理解它们的特征。
本文中涉及关键知识点
1.异步编程
2.线程的使用
3.基于任务的异步模式
4.并行编程
5.总结
异步编程
什么是异步操作?异步操作是指某些操作能够独立运行,不依赖主流程或主其他处理流程。通常情况下,C#程序从Main方法开始,当Main方法返回时结束。所有的操作都是按顺序执行的。执行操作是有序列的,一个操作必须等到其前面的操作完成才能够执行。如以下代码示例:staticvoidMain(string[]args)
[code]
{
DoTaskOne();
DoTaskTwo();
}
[/code]
“DoTaskOne”方法结束后,DoTaskTwo()才能够执行。
异步编程中常用后台运行的方法体现,主调用线程不会被阻塞。调用后台运行的方法后,执行流程会立即返回到调用的线程并继续执行其他任务。后台运行方法通常是用线程或任务来实现。
在上面的例子中,在“DoTaskOne”方法调用成功后,如果“DoTaskOne”是异步调用,,执行流程立即返回到Main方法中,并继续执行“DoTaskTwo”方法。
C#提供了Thread类创建线程实现异步编程,或者使用.NET提供的异步模式实现异步编程。.NET中提供了三种不同的异步模式:
1.异步编程模型(APM)模式
2.基于事件的异步模式(EAP)
3.基于任务的异步模式(TAP)
前两种模型微软官方并不推荐使用,本文不再详细描述。我们将详细讨论基于任务的异步模式(TAP):
线程的使用
在.NET4.5中引入了异步编程模式,大部分情况下都不需要我们手动创建线程。编译器已经替代了开发人员来完成这项工作。创建新线程是非常耗时的。一般情况下,异步和并行编程使用“基于任务的异步模式(TAP)”和“任务并行库(TPL)”就够了。如果需要控制线程的功能则需要使用其他模式。
TAP和TPL都是基于任务。一般来说任务是从线程池中调用线程(线程池是.NET框架创建的和维护的线程集。如果我们使用任务,就不需要直接调用线程池。
任务可以在以下情况运行:
1.在正在运行的线程中
2.在新线程中
3.从线程池中的某一线程中
4.没有线程也可以运行
如果使用任务机制,开发人员就不必担心线程的创建或使用,.NET框架已经为我们解决了这一难题。
有时候需要控制线程,执行以下操作:
1.设置线程名称
2.设置线程优先级
3.设置线程是前端或后端运行
我们可以使用线程类来创建线程。
使用Thread类创建线程
Thread类的构造函数接收委托类型的参数1.ThreadStart:定义了返回值为空的方法,且不带参数的方法。
2.ParameterizedThreadStart:定义了返回值为空且有一个object类型的参数。
下面是一个简单的例子,使用Start方法启动一个新线程:
staticvoidMain(string[]args)
[code]
{
Threadthread=newThread(DoTask);
thread.Start();//StartDoTaskmethodinanewthread
//Doothertasksinmainthread
}
staticpublicvoidDoTask(){
//dosomethinginanewthread
}
[/code]
可以用Lamda表达式代替线程名称:
staticvoidMain(string[]args)
[code]
{
Threadthread=newThread(()=>{
//dosomethinginanewthread
});
thread.Start();//Startanewthread
//Doothertasksinmainthread
}
[/code]
如果不需要引用变量,可如下直接启动线程:
staticvoidMain(string[]args)
[code]
{
newThread(()=>{
//dosomethinginanewthread
}).Start();//Startanewthread
//Doothertasksinmainthread
}
[/code]
但是,如果想控制线程对象,对线程设置一些属性,需要在线程创建后引用线程变量。如下可给线程对象的不同属性设值:
staticvoidMain(string[]args)
[code]
{
Threadthread=newThread(DoTask);
thread.Name="Mynewthread";//Asigningnametothethread
thread.IsBackground=false;//Madethethreadforground
thread.Priority=ThreadPriority.AboveNormal;//Settingthreadpriority
thread.Start();//StartDoTaskmethodinanewthread
//Doothertaskinmainthread
}
[/code]
调用引用变量,可以执行一些操作如中止线程或通过调用join方法等待阻塞线程。
如果需要通过函数传值,可以给Start方法传值。由于该方法的参数为Object类型,因此需要强制转换类型。
staticvoidMain(string[]args)
[code]
{
Threadthread=newThread(DoTaskWithParm);
thread.Start("Passingstring");//StartDoTaskWithParmmethodinanewthread
//Doothertaskinmainthread
}
staticpublicvoidDoTaskWithParm(objectdata)
{
//weneedtocastthedatatoappropriateobject
}
[/code]
“async”和“await”关键字
.NET框架引入了两个新的关键字来实现异步编程:“async”和“await”。使用“await”的异步方法必须由“async”修饰符来声明方法。“await”关键字修饰调用异步方法。await运算符应用于一个异步方法中的任务以挂起该方法的执行,直到等待任务完成.如下:privateasyncstaticvoidCallerWithAsync()//asyncmodifierisused
[code]
{
stringresult=awaitGetSomethingAsync();//awaitisusedbeforeamethodcall.Itsuspends
//executionofCallerWithAsync()methodandcontrolreturstothecallingthreadthatcan
//performothertask.
Console.WriteLine(result);
//thislinewouldnotbeexecutedbeforeGetSomethingAsync()//methodcompletes
}
[/code]
而“async”修饰符只能用于返回值为Task类型或Void的方法。它不能用于主程序的切入点。
所有的方法之前不能使用await关键字,使用“await”关键字方法必须返回“可等待”类型。以下属于“可等待”类型:
1.Task
2.Task<T>
3.自定义“可等待”类型。
基于任务的异步模式
首先我们需要声明一个返回类型为Task或Task<T>的异步方法。可以通过以下几种方式创建任务:1.Task.Factory.StartNew方法:在之前的.NET版本(在.NET4中),是创建和启动任务的主要方法。
2.Task.Run或Task.Run<T>方法:从.NET4.5这个方法已经被使用。此方法足以满足常见情况。
3.Task.FromResult方法:如果结果是已计算,就可以用这个方法来创建任务。
创建并等待一个任务
使用Task.Run<T>方法创建Task。该方法将特定工作按顺序排列在线程池中运行,并返回工作的任务句柄。需要以下步骤从同步方法中创建异步任务:1.假设下面方法是同步的,但需要一定的时间来完成:
staticstringGreeting(stringname)
[code]
{
Thread.Sleep(3000);
returnstring.Format("Hello,{0}",name);
}
[/code]
2.要以异步方式访问此方法,必须以异步方式封装。命名为“GreetingAsync”。增加“Async”的后缀命名异步方法。
staticTask<string>GreetingAsync(stringname)
[code]
{
returnTask.Run<string>(()=>
{
returnGreeting(name);
});
}
[/code]
3.现在,可通过使用的await关键字调用异步方法GreetingAsync
privateasyncstaticvoidCallWithAsync()
[code]
{
//someothertasks
stringresult=awaitGreetingAsync("Bulbul");
//Wecanaddmultiple“await”insame“async”method
//stringresult1=awaitGreetingAsync(“Ahmed”);
//stringresult2=awaitGreetingAsync(“EveryBody”);
Console.WriteLine(result);
}
[/code]
当“CallWithAsync”方法被调用时,与常规的同步方法一样执行,直到遇到“await”的关键字。当它执行到await的关键字会处理执行,并开始等待“GreetingAsync(”Bulbul“)”方法被完成。同时,程序流将返回”CallWithAsync“方法的调用者,并继续执行调用者的任务。
当“GreetingAsync("Bulbul")方法完成,“CallWithAsync”的方法恢复“await关键字后的其他任务。在本实例中,将继续执行的代码“Console.WriteLine(result)”
4.使用任务持续:Task类“ContinueWith”的方法定义了Task完成后被调用的代码。
privatestaticvoidCallWithContinuationTask()
[code]
{
Task<string>t1=GreetingAsync("Bulbul");
t1.ContinueWith(t=>
{
stringresult=t.Result;
Console.WriteLine(result);
});
}
[/code]
如果使用“ContinueWith”的方法就不需要使用“await“关键字,编译器会自动在合适的位置中添加“await”关键字。
等候多个异步方法。
看看下面的代码:
privateasyncstaticvoidCallWithAsync()
[code]
{
stringresult=awaitGreetingAsync("Bulbul");
stringresult1=awaitGreetingAsync(“Ahmed”);
Console.WriteLine(result);
Console.WriteLine(result1);
}
[/code]
有两个正在等待调用函数序列。“GreetingAsync(”Ahmed“)”会在完成第一个呼叫“GreetingAsync(”Bulbul“)”之后启动。如果“result”和上面的代码“result1”是独立的,那么连续的“awiating”并不是一个好的做法。
在这种情况下,我们可以简化调用方法,不需要添加多个“await”关键字,只在一个地方添加await关键字,如下所示,这种情况下,该方法的调用都可以并行执行。
privateasyncstaticvoidMultipleAsyncMethodsWithCombinators()
[code]
{
Task<string>t1=GreetingAsync("Bulbul");
Task<string>t2=GreetingAsync("Ahmed");
awaitTask.WhenAll(t1,t2);
Console.WriteLine("Finishedbothmethods.\n"+
"Result1:{0}\nResult2:{1}",t1.Result,t2.Result);
}
[/code]
在这里,我们使用Task.WhenAll连接器。Task.WhenAll创建一个任务,将完成所有的提供的任务。Task类也有其他的结合器。Task.WhenAny,当所任务链中所有的任务完成时,结束使用。
处理异常
必须把“await的代码块放在try块内捕获异常。privateasyncstaticvoidCallWithAsync()
[code]
{
try
{
stringresult=awaitGreetingAsync("Bulbul");
}
catch(Exceptionex)
{
Console.WriteLine(“handled{0}”,ex.Message);
}
}
[/code]
如果try块中有多个“await”,只有第一个”await“异常会被处理,其他“await”将无法被捕捉。如果希望所有的方法都能捕获异常,不能使用“await”关键字调用方法,使用Task.WhenAll来执行任务。
privateasyncstaticvoidCallWithAsync()
[code]
{
try
{
Task<string>t1=GreetingAsync("Bulbul");
Task<string>t2=GreetingAsync("Ahmed");
awaitTask.WhenAll(t1,t2);
}
catch(Exceptionex)
{
Console.WriteLine(“handled{0}”,ex.Message);
}
}
[/code]
捕获所有任务的错误一种方法是在try块之外声明任务,这样可以从try块进行访问,并检查任务的“IsFaulted”属性。如果它存在异常那么“IsFaulted”属性值为True,就可捕获任务实例的内部异常。
还有另一个更好的办法:
staticasyncvoidShowAggregatedException()
[code]
{
TasktaskResult=null;
try
{
Task<string>t1=GreetingAsync("Bulbul");
Task<string>t2=GreetingAsync("Ahmed");
await(taskResult=Task.WhenAll(t1,t2));
}
catch(Exceptionex)
{
Console.WriteLine("handled{0}",ex.Message);
foreach(varinnerExintaskResult.Exception.InnerExceptions)
{
Console.WriteLine("innerexception{0}",nnerEx.Message);}
}
}
[/code]
取消任务
在此之前,如果从线程池中调用线程,线程是不可能取消。现在,Task类提供了一个方法基于CancellationTokenSource类能够取消已启动的任务,取消任务步骤:1.异步方法应该除外“CancellationToken”参数类型
2.创建CancellationTokenSource类实例:
varcts=newCancellationTokenSource();
3.传递CancellationToken,如:
Task<string>t1=GreetingAsync("Bulbul",cts.Token);
4.长时间运行的方法中,必须调用CancellationToken的ThrowIfCancellationRequested()方法。
staticstringGreeting(stringname,CancellationTokentoken){
[code]
Thread.Sleep(3000);
token.ThrowIfCancellationRequested();
returnstring.Format("Hello,{0}",name);
}
5.从等待的Task中捕获OperationCanceledException异常。
[/code]
6.如果通过调用CancellationTokenSource的实例的方法执行取消操作,将从长时间运行操作中抛出OperationCanceledException异常。也可以设置取消的时间。以下是完整的代码,一秒后执行取消操作:
staticvoidMain(string[]args)
[code]
{
CallWithAsync();
Console.ReadKey();
}
asyncstaticvoidCallWithAsync()
{
try
{
CancellationTokenSourcesource=newCancellationTokenSource();
source.CancelAfter(TimeSpan.FromSeconds(1));
vart1=awaitGreetingAsync("Bulbul",source.Token);
}
catch(OperationCanceledExceptionex)
{
Console.WriteLine(ex.Message);
}
}
staticTask<string>GreetingAsync(stringname,CancellationTokentoken)
{
returnTask.Run<string>(()=>
{
returnGreeting(name,token);
});
}
staticstringGreeting(stringname,CancellationTokentoken)
{
Thread.Sleep(3000);
token.ThrowIfCancellationRequested();
returnstring.Format("Hello,{0}",name);
}
[/code]
并行编程
.NET4.5及以上版本推出“Parallel类,是线程类的抽象。使用“Parallel”类,我们可以实现并行。并行与线程不同,它使用所有可用的CPU或内核的。以下两种类型的并行是可行:数据并行:如果我们有数据的大集合,我们希望在每个数据的某些操作进行并行使用,那么就可以使用数据并行。Parallel类有静态For或ForEach来执行数据并行行,如
ParallelLoopResultresult=
[code]Parallel.For(0,100,async(inti)=>
{
Console.WriteLine("{0},task:{1},thread:{2}",i,
Task.CurrentId,Thread.CurrentThread.ManagedThreadId);
awaitTask.Delay(10);
});
[/code]
For或ForEach方法可以在多线程中和且索引无序可以是无序的。
如果想停止并行For或ForEach方法,可通过ParallelLoopState作为参数,并根据需要打破循环的状态,跳出循环。
ParallelLoopResultresult=
[code]Parallel.For(0,100,async(inti,ParallelLoopStatepls)=>
{
Console.WriteLine("{0},task:{1},thread:{2}",i,
Task.CurrentId,Thread.CurrentThread.ManagedThreadId);
awaitTask.Delay(10);
if(i>5)pls.Break();
});
[/code]
2.任务并行:如果想要同时运行多个任务的,我们可以通过调用Parallel类的invoke方法使用任务并行Parallel.Invoke方法接收委托行为的数组。例如:
staticvoidParallelInvoke()
[code]
{
Parallel.Invoke(MethodOne,MethodTwo);
}
[/code]
结论
本文详细介绍了.NETFramework4.5提供的异步编程技术及细节。原文链接:http://www.codeproject.com/Articles/996857/Asynchronous-programming-and-Threading-in-Csharp-N
相关文章推荐
- C#和.net之间的关系
- SmartUpload实现下载的简单代码
- c语言中内存布局问题
- Effective C++ 条款47
- Qt下QTableWidget的使用
- 【原创首发】针对java初学者的环境变量配置工具
- Eclipse Other Projects小问题
- golang vim ide 环境搭建
- Qt中出现的错误总结
- PHP中会话处理函数的逻辑流程
- python
- C#实现一个简单的 Restful Service
- Java web学习入门
- 使用gdb调试C++程序时,感觉不按流程执行,跳来跳去的解决办法
- asp.net 导入excel表格
- RubyGem默认源安装太慢,修改国内淘宝源
- C++模板
- java处理barCode条形码
- C++入门程序作业2
- java实现条形码