分享
 
 
 

C#:异步编程和线程的使用(.NET4.5)

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: |||超大  

异步编程和线程处理是并发或并行编程非常重要的功能特征。为了实现异步编程,可使用线程也可以不用。将异步与线程同时讲,将有助于我们更好的理解它们的特征。

本文中涉及关键知识点

1. 异步编程

2. 线程的使用

3. 基于任务的异步模式

4. 并行编程

5. 总结

异步编程什么是异步操作?异步操作是指某些操作能够独立运行,不依赖主流程或主其他处理流程。通常情况下,C#程序从Main方法开始,当Main方法返回时结束。所有的操作都是按顺序执行的。执行操作是有序列的,一个操作必须等到其前面的操作完成才能够执行。如以下代码示例:

1:staticvoidMain(string[] args)

2:

3:{

4:

5:DoTaskOne();

6:

7:DoTaskTwo();

8:

9:}

“DoTaskOne”方法结束后,DoTaskTwo()才能够执行。

异步编程中常用后台运行的方法体现,主调用线程不会被阻塞。调用后台运行的方法后,执行流程会立即返回到调用的线程并继续执行其他任务。后台运行方法通常是用线程或任务来实现。

在上面的例子中,在“DoTaskOne”方法调用成功后,如果“DoTaskOne”是异步调用,,执行流程立即返回到Main方法中,并继续执行“DoTaskTwo” 方法。

C#提供了Thread类创建线程实现异步编程,或者使用.NET提供的异步模式实现异步编程。.NET中提供了三种不同的异步模式:

1. 异步编程模型(APM)模式

2. 基于事件的异步模式(EAP)

3. 基于任务的异步模式(TAP)

前两种模型微软官方并不推荐使用,本文不再详细描述。我们将详细讨论基于任务的异步模式(TAP):

线程的使用在.NET 4.5中引入了异步编程模式,大部分情况下都不需要我们手动创建线程。编译器已经替代了开发人员来完成这项工作。

创建新线程是非常耗时的。一般情况下,异步和并行编程使用 “基于任务的异步模式(TAP)”和“任务并行库(TPL)”就够了。如果需要控制线程的功能则需要使用其他模式。

TAP和TPL都是基于任务。一般来说任务是从线程池中调用线程( 线程池是.NET框架创建的和维护的线程集。如果我们使用任务,就不需要直接调用线程池。

任务可以在以下情况运行:

1. 在正在运行的线程中

2. 在新线程中

3. 从线程池中的某一线程中

4. 没有线程也可以运行

如果使用任务机制,开发人员就不必担心线程的创建或使用,.NET框架已经为我们解决了这一难题。

有时候需要控制线程,执行以下操作:

1. 设置线程名称

2. 设置线程优先级

3. 设置线程是前端或后端运行

我们可以使用线程类来创建线程。

使用Thread类创建线程Thread类的构造函数接收委托类型的参数

1. ThreadStart:定义了返回值为空的方法,且不带参数的方法。

2. ParameterizedThreadStart:定义了返回值为空且有一个object类型的参数。

下面是一个简单的例子,使用 Start方法启动一个新线程:

1:staticvoidMain(string[] args)

2:

3:{

4:

5:Thread thread =newThread(DoTask);

6:

7:thread.Start();// Start DoTask method in a new thread

8:

9://Do other tasks in main thread

10:

11:}

12:

13:staticpublicvoidDoTask() {

14:

15://do something in a new thread

16:

17:}

可以用Lamda表达式代替线程名称:

1:staticvoidMain(string[] args)

2:

3:{

4:

5:Thread thread =newThread(() => {

6:

7://do something in a new thread

8:

9:});

10:

11:thread.Start();// Start a new thread

12:

13://Do other tasks in main thread

14:

15:}

如果不需要引用变量,可如下直接启动线程:

1:staticvoidMain(string[] args)

2:

3:{

4:

5:newThread(() => {

6:

7://do something in a new thread

8:

9:}).Start();// Start a new thread

10:

11://Do other tasks in main thread

12:

13:}

但是,如果想控制线程对象,对线程设置一些属性,需要在线程创建后引用线程变量。如下可给线程对象的不同属性设值:

1:staticvoidMain(string[] args)

2:

3:{

4:

5:Thread thread =newThread(DoTask);

6:

7:thread.Name ="My new thread";// Asigning name to the thread

8:

9:thread.IsBackground =false;// Made the thread forground

10:

11:thread.PRiority = ThreadPriority.AboveNormal;// Setting thread priority

12:

13:thread.Start();// Start DoTask method in a new thread

14:

15://Do other task in main thread

16:

17:}

调用引用变量,可以执行一些操作如中止线程或通过调用join方法等待阻塞线程。

如果需要通过函数传值,可以给Start方法传值。由于该方法的参数为Object类型,因此需要强制转换类型。

1:staticvoidMain(string[] args)

2:

3:{

4:

5:Thread thread =newThread(DoTaskWithParm);

6:

7:thread.Start("Passing string");// Start DoTaskWithParm method in a new thread

8:

9://Do other task in main thread

10:

11:}

12:

13:staticpublicvoidDoTaskWithParm(objectdata)

14:

15:{

16:

17://we need to cast the data to appropriate object

18:

19:}

“async”和“await”关键字.NET框架引入了两个新的关键字来实现异步编程:“async”和“await”。使用 “await”的异步方法必须由“async”修饰符来声明方法。“await”关键字修饰调用异步方法。await 运算符应用于一个异步方法中的任务以挂起该方法的执行,直到等待任务完成.如下:

1:privateasyncstaticvoidCallerWithAsync()// async modifier is used

2:

3:{

4:

5:stringresult = await GetSomethingAsync();// await is used before a method call. It suspends

6://execution of CallerWithAsync() method and control returs to the calling thread that can

//perform other task.

7:

8:Console.WriteLine(result);

9:// this line would not be executed before GetSomethingAsync() //method completes

10:

11:}

而“ async ”修饰符只能用于返回值为Task类型或Void的方法。它不能用于主程序的切入点。

所有的方法之前不能使用await关键字,使用“await”关键字方法必须返回 “可等待”类型。以下属于“可等待”类型:

1. Task

2. Task<T>

3. 自定义“可等待”类型。

基于任务的异步模式首先我们需要声明一个返回类型为Task或Task<T>的异步方法。可以通过以下几种方式创建任务:

1. Task.Factory.StartNew方法:在之前的.NET版本(在.NET 4中),是创建和启动任务的主要方法。

2. Task.Run或Task.Run <T>方法:从.NET 4.5这个方法已经被使用。此方法足以满足常见情况。

3. Task.FromResult方法:如果结果是已计算,就可以用这个方法来创建任务。

创建并等待一个任务使用Task.Run <T>方法创建Task。该方法将特定工作按顺序排列在线程池中运行,并返回工作的任务句柄。需要以下步骤从同步方法中创建异步任务:

1. 假设下面方法是同步的,但需要一定的时间来完成:

1:staticstringGreeting(stringname)

2:

3:{

4:

5:Thread.Sleep(3000);

6:

7:returnstring.Format("Hello, {0}", name);

8:

9:}

2. 要以异步方式访问此方法,必须以异步方式封装。命名为“GreetingAsync”。增加“Async”的后缀命名异步方法。

1:staticTask<string> GreetingAsync(stringname)

2:

3:{

4:

5:returnTask.Run<string>(() =>

6:

7:{

8:

9:returnGreeting(name);

10:

11:});

12:

13:}

3.现在,可通过使用的await关键字调用异步方法GreetingAsync

1:privateasyncstaticvoidCallWithAsync()

2:

3:{

4:

5://some other tasks

6:

7:stringresult = await GreetingAsync("Bulbul");

8:

9://We can add multiple “await” in same “async” method

10:

11://string result1 = await GreetingAsync(“Ahmed”);

12:

13://string result2 = await GreetingAsync(“Every Body”);

14:

15:Console.WriteLine(result);

16:

17:}

当“CallWithAsync”方法被调用时,与常规的同步方法一样执行,直到遇到“await”的关键字。当它执行到 await的关键字会处理执行,并开始等待“GreetingAsync(” Bulbul “)” 方法被完成。同时,程序流将返回” CallWithAsync “方法的调用者,并继续执行调用者的任务。

当“GreetingAsync(" Bulbul ") 方法完成,“CallWithAsync”的方法恢复 “await关键字后的其他任务。在本实例中,将继续执行的代码“Console.WriteLine(result)”

4. 使用任务持续:Task类 “ContinueWith”的方法定义了Task完成后被调用的代码。

1:privatestaticvoidCallWithContinuationTask()

2:

3:{

4:

5:Task<string> t1 = GreetingAsync("Bulbul");

6:

7:t1.ContinueWith(t =>

8:

9:{

10:

11:stringresult = t.Result;

12:

13:Console.WriteLine(result);

14:

15:});

16:

17:}

如果使用“ContinueWith”的方法就不需要使用“await“关键字,编译器会自动在合适的位置中添加“await”关键字。

等候多​​个异步方法。

看看下面的代码:

1:privateasyncstaticvoidCallWithAsync()

2:

3:{

4:

5:stringresult = await GreetingAsync("Bulbul");

6:

7:stringresult1 = await GreetingAsync(&ldquo;Ahmed&rdquo;);

8:

9:Console.WriteLine(result);

10:

11:Console.WriteLine(result1);

12:

13:}

有两个正在等待调用函数序列。“GreetingAsync(” Ahmed “)” 会在完成第一个呼叫“GreetingAsync(” Bulbul “)” 之后启动。如果“result”和上面的代码“result1”是独立的,那么连续的“awiating”并不是一个好的做法。

在这种情况下,我们可以简化调用方法,不需要添加多个“await”关键字,只在一个地方添加await关键字,如下所示,这种情况下,该方法的调用都可以并行执行。

1:privateasyncstaticvoidMultipleAsyncMethodsWithCombinators()

2:

3:{

4:

5:Task<string> t1 = GreetingAsync("Bulbul");

6:

7:Task<string> t2 = GreetingAsync("Ahmed");

8:

9:await Task.WhenAll(t1, t2);

10:

11:Console.WriteLine("Finished both methods.\n "+

12:

13:"Result 1: {0}\n Result 2: {1}", t1.Result, t2.Result);

14:

15:}

在这里,我们使用Task.WhenAll连接器。Task.WhenAll创建一个任务,将完成所有的提供的任务。Task类也有其他的结合器。Task.WhenAny,当所任务链中所有的任务完成时,结束使用。

处理异常必须把“await的代码块放在try块内捕获异常。

1:privateasyncstaticvoidCallWithAsync()

2:

3:{

4:

5:try

6:

7:{

8:

9:stringresult = await GreetingAsync("Bulbul");

10:

11:}

12:

13:catch(Exception ex)

14:

15:{

16:

17:Console.WriteLine(&ldquo;handled {0}&rdquo;, ex.Message);

18:

19:}

20:

21:}

如果try块中有多个“await”,只有第一个”await“异常会被处理,其他“await”将无法被捕捉。如果希望所有的方法都能捕获异常,不能使用“await”关键字调用方法,使用Task.WhenAll来执行任务。

1:privateasyncstaticvoidCallWithAsync()

2:

3:{

4:

5:try

6:

7:{

8:

9:Task<string> t1 = GreetingAsync("Bulbul");

10:

11:Task<string> t2 = GreetingAsync("Ahmed");

12:

13:await Task.WhenAll(t1, t2);

14:

15:}

16:

17:catch(Exception ex)

18:

19:{

20:

21:Console.WriteLine(&ldquo;handled {0}&rdquo;, ex.Message);

22:

23:}

24:

25:}

捕获所有任务的错误一种方法是在try块之外声明任务,这样可以从try块进行访问,并检查任务的“IsFaulted”属性。如果它存在异常那么“IsFaulted”属性值为True,就可捕获任务实例的内部异常。

还有另一个更好的办法:

1:staticasyncvoidShowAggregatedException()

2:

3:{

4:

5:Task taskResult =null;

6:

7:try

8:

9:{

10:Task<string> t1 = GreetingAsync("Bulbul");

11:

12:Task<string> t2 = GreetingAsync("Ahmed");

13:

14:await (taskResult = Task.WhenAll(t1, t2));

15:

16:}

17:

18:catch(Exception ex)

19:

20:{

21:

22:Console.WriteLine("handled {0}", ex.Message);

23:

24:foreach(var innerExintaskResult.Exception.InnerExceptions)

25:

26:{

27:Console.WriteLine("inner exception {0}", nnerEx.Message); }

28:}

29:

30:}

取消任务在此之前,如果从线程池中调用线程,线程是不可能取消。现在,Task类提供了一个方法基于CancellationTokenSource类能够取消已启动的任务,取消任务步骤:

1. 异步方法应该除外 “ CancellationToken” 参数类型

2. 创建CancellationTokenSource类实例:

var cts =newCancellationTokenSource();

3. 传递CancellationToken,如:

1:Task<string> t1 = GreetingAsync("Bulbul", cts.Token);

4. 长时间运行的方法中,必须调用CancellationToken的ThrowIfCancellationRequested()方法。

1:staticstringGreeting(stringname, CancellationToken token){

2:

3:Thread.Sleep(3000);

4:

5:token. ThrowIfCancellationRequested();

6:

7:returnstring.Format("Hello, {0}", name);

8:

9:}

5. 从等待的Task中捕获OperationCanceledException异常。

6. 如果通过调用CancellationTokenSource的实例的方法执行取消操作,将从长时间运行操作中抛出OperationCanceledException异常。也可以设置取消的时间。以下是完整的代码,一秒后执行取消操作:

1:staticvoidMain(string[] args)

2:

3:{

4:CallWithAsync();

5:

6:Console.ReadKey();

7:

8:}

9:

10:

11:asyncstaticvoidCallWithAsync()

12:

13:{

14:

15:try

16:

17:{

18:

19:CancellationTokenSource source =newCancellationTokenSource();

20:

21:source.CancelAfter(TimeSpan.FromSeconds(1));

22:

23:var t1 = await GreetingAsync("Bulbul", source.Token);

24:}

25:

26:catch(OperationCanceledException ex)

27:

28:{

29:

30:Console.WriteLine(ex.Message);

31:

32:}

33:

34:}

35:

36:staticTask<string> GreetingAsync(stringname, CancellationToken token)

37:

38:{

39:

40:returnTask.Run<string>(() =>

41:

42:{

43:

44:returnGreeting(name, token);

45:

46:});

47:}

48:

49:

50:staticstringGreeting(stringname, CancellationToken token)

51:

52:{

53:

54:Thread.Sleep(3000);

55:token.ThrowIfCancellationRequested();

56:

57:returnstring.Format("Hello, {0}", name);

58:

59:}

60:

并行编程.NET 4.5及以上版本推出“Parallel类,是线程类的抽象。使用“Parallel”类,我们可以实现并行。并行与线程不同,它使用所有可用的CPU或内核的。以下两种类型的并行是可行:

数据并行:如果我们有数据的大集合,我们希望在每个数据的某些操作进行并行使用,那么就可以使用数据并行。Parallel类有静态For或ForEach来执行数据并行行,如1:ParallelLoopResult result =

2:Parallel.For(0, 100, async (inti) =>

3:{

4:Console.WriteLine("{0}, task: {1}, thread: {2}", i,

5:Task.CurrentId, Thread.CurrentThread.ManagedThreadId);

6:await Task.Delay(10);

7:

8:});

For或ForEach方法可以在多线程中和且索引无序可以是无序的。

如果想停止并行For或ForEach方法,可通过ParallelLoopState作为参数,并根据需要打破循环的状态,跳出循环。

1:ParallelLoopResult result =

2:Parallel.For(0, 100, async (inti, ParallelLoopState pls) =>

3:{

4:Console.WriteLine("{0}, task: {1}, thread: {2}", i,

5:Task.CurrentId, Thread.CurrentThread.ManagedThreadId);

6:await Task.Delay(10);

7:if(i > 5) pls.Break();

8:});

2. 任务并行:如果想要同时运行多个任务的,我们可以通过调用Parallel类的invoke方法使用任务并行Parallel.Invoke方法接收委托行为的数组。例如:

1:staticvoidParallelInvoke()

2:

3:{

4:

5:Parallel.Invoke(MethodOne, MethodTwo);

6:

7:}

8:

结论本文详细介绍了.NET Framework 4.5提供的异步编程技术及细节。

原文链接:http://www.codeproject.com/Articles/996857/Asynchronous-programming-and-Threading-in-Csharp-N

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有