.NET异步程序设计之async&await

shanzm-2020年3月7日 23:12:53

0.背景引入

如今的.net异步编程中,通常都是使用 基于任务异步模式(Task-based Asynchronous Pattern,TAP)实现异步编程git

可参考微软文档:使用基于任务的异步模式github

基于任务的异步模式,是使用System.Threading.Tasks.Task 命名空间中的System.Threading.Tasks.Task<TResult>System.Threading.Tasks类型实现异步编程,编程

配合着C#5.0(.net 4.5)中添加的两个关于异步编程的关键字asyncawait,能够快速的实现异步操做。数组




1.async和await基本语法

1.1 简介

在C#5.0(.net 4.5)中添加了两个关于异步编程的关键字asyncawait服务器

两个关键字能够快速的建立和使用异步方法。asyncawait关键字只是编译器功能,编译后会用Task类建立代码,实现异步编程。其实只是C#对异步编程的语法糖,本质上是对Task对象的包装操做。多线程

因此,若是不使用这两个关键字,也能够用C#4.0中Task类的方法来实现一样的功能,只是没有那么方便。(见:.NET异步程序设计之任务并行库)mvc

1.2 具体使用方法

  1. async:用于异步方法的函数名前作修饰符,处于返回类型左侧。async只是一个标识符,只是说明该方法中有一个或多个await表达式,async自己并不建立异步操做。异步

  2. await:用于异步方法内部,用于指明须要异步执行的操做(称之为await表达式),注意一个异步方法中能够有多个await表达式,并且应该至少有一个(如果没有的话编译的时候会警告,但仍是能够构建和执行,只不过就至关于同步方法而已)。async

    其实这里你有没有想起Task类中为了实现延续任务而使用的等待者,好比:使用task.GetAwaiter()方法为task类建立一个等待者(能够参考;3.3.2使用Awaiter)。await关键字就是基于 .net 4.5中的awaiter对象实现的。

  3. 总而言之,如果有await表达式则函数必定要用async修饰,如果使用了async修饰的方法中没有await表达式,编译的时候会警告!

1.3 返回值类型

  1. 使用asyncawait定义的异步方法的返回值只有三种:Task<T>Taskvoid

  2. 异步方法中有return语句,则返回值类型为Task<T>,其中T是return语句返回对象的类型。(编译器帮咱们把T类型数据转换为Task<T>类型)。因此不要使用return返回一个Task<T>类型的对象,而是只须要返回一个T类型数据便可

  3. 异步方法中没有return语句,可是你须要查看异步方法的状态或是要对Task对象操做(好比task.Wait()),则可定义返回值类型为Task

  4. 异步方法中没有return语句且不须要查看异步方法的状态,则可认为是返回值是void类型。此时称之为“调用并忘记”(fire and forget),其实这并非一个很好的用法(具体的分析能够看:异步方法的异常处理)!

  5. 如果真的须要返回一个Task<T>类型的数据 (好比在非async修饰的方法中,定义返回值类型就是一个Task<T>类型),则:

    • return Task.Run(()=>{……})

    • T类型转换为Task<T>类型:return Task.FromResult(T data)

    • IAsyncResult类型对象转换为Task类型:return Task.Factory.FromTask(IAsyncResult data)

1.4 其余细节

  1. async修饰符只能用于返回TaskTask<T>viod的方法,或者Lambda表达式中。

  2. async不能用于程序的入口点,即Main()不能使用async修饰符。 谢谢@coredx提醒:C#7.1中应用程序的入口点能够具备async修饰符,参考:What's new in C# 7.1

1.5 async传染性

  1. 简单的说:函数体中含有await表达式的函数,必须使用async修饰!

    而一个使用了async修饰的方法,在调用它的时候若有必要则必须使用await等待!

    使用了await等待的方法,则其调用方法又必须使用async修饰,这从而造成了一个循环,这就是async传染

    换句话说就是哪里调用了async修饰的方法则async就会传染到哪!

    能够有的时候咱们并不想要咱们的方法变为async修饰的方法,因此须要避免async传染

    避免的主要方法就是使用延续任务来避免,你想想以前在Task类中,使用延续任务时,主要就是避免使用await等待!

    参考:C# 5.0中同步执行异步方法

  2. 以前咱们说了:主函数Main()不能使用async修饰符,由于Main函数不可以是异步方法,这也就意味着一切的异步方法最终的调用方法必定是同步方法(C#7.1Main能够是异步的),而调用异步方法的那个同步方法,称之为病源隔断方法,由于在这里开始,再也不会发生async传染。

1.6 简单示例

示例1:定义一个异步方法,并调用它

static void Main(string[] args)
{   
    Task<int> t = SumAsync(1, 2);
    t.ContinueWith(t => Console.WriteLine( "异步操做结果为:" + t.Result));
    for (int i = 0; i < 10; i++)
    {
        Thread.Sleep(1000);
        Console.WriteLine($"循环次数{i}");
    }
    Console.ReadKey();
}

private static async Task<int> SumAsync(int num1, int num2)
{
    int sum = await Task.Run(() => { Thread.Sleep(3000); return num1 + num2; });
    return sum;
}

示例说明

  1. 异步方法SumAsync()函数体中返回的是整型sum,即返回的是一个整型,可是在声明函数时,返回值需写为:Task<int>

    反过来讲:如果异步方法的返回值类型为Task<T>,则在方法中只须要返回T类型的数据

    这一点就是和Task.Run()的返回值的书写方式同样,即若Task.Run()参数是有返回值的委托Func<TResult>,则Task.Run()返回值是Task<TResult>泛型

  2. 异步方法的命名默认是在最后加"Async",即"XXXAsync"。

  3. 调用异步方法的方法称之为调用方法(在这里就是主函数Main()),调用方法和被调用的异步方法,不必定在不一样的线程中

  4. 其实你看上面示例的代码也会发现,单独把异步操做封装为一个异步方法,这样能够为异步操做传参!
    你能够还记得的在.net异步程序设计之任务并行库中,屡次说明Task.Run()的参数只能是无参委托。

  5. 有一点在这里说明一下:关于异步匿名方法,或是异步Lambda表达式。
    在为一个事件绑定事件处理程序的时候,对于一些简单的事件处理程序,咱们可使用Lambda表达式
    可是咱们想要异步操做Lambda表达式,则能够直接写为:

Butten.Click+=async(sender,e)=>{...await...}

详细能够参照《C#图解教程:20.5使用异步Lambda表达式》




2.异步方法的执行顺序

依旧是上面的示例,咱们在每一个操做中、操做前、操做后都打印其当前所处的线程,仔细的观察,异步方法的执行顺序。

再次强调,这里用async修饰的方法,称之为异步方法,这里调用该异步方法的方法,称之为调用方法

代码:

//调用方法
static void Main(string[] args)
{
    Console.WriteLine($"-1-.正在执行的线程,线程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------调用方法中调用异步方法以前的代码");
    Task<int> result = SumAsync(1, 2);
    Console.WriteLine($"-3-.正在执行的线程,线程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------调用方法中调用异步方法以后的代码");
    result.ContinueWith(t => Console.WriteLine($"-8-.正在执行的线程,线程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------这是延续任务的线程" + "-异步操做结果为:" + result.Result));
    Console.WriteLine($"-4-.正在执行的线程,线程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------调用方法中延续任务以后的代码");
    for (int i = 0; i < 5; i++)
    {
        Thread.Sleep(1000);
        Console.WriteLine($"正在执行的线程,线程ID:{Thread.CurrentThread.ManagedThreadId,2}"+$"循环次数{i}--------------------------------------调用方法中延续任务以后的代码");
    }
    Console.ReadKey();
}

//异步方法
private static async Task<int> SumAsync(int num1, int num2)
{
    Console.WriteLine($"-2-.正在执行的线程,线程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------异步方法中await表达式以前的代码");
    int sum1 = await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"-5-.正在执行的线程,线程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------这是第一个await表达式的线程"); return num1 + num2; });
    Console.WriteLine($"-6-.正在执行的线程,线程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------异步方法中await表达式以后的代码");
    int sum2=await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"-7-.正在执行的线程,线程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------这是第二个await表达式的线程"); return num1 + num2; });
    return sum1+sum2;
}

运行结果:

说明

注意运行顺序:

调用异步方法方法后,按照同步顺序执行异步方法中await表达式以前的代码,

当运行到第1个await表达式后,建立一个新的线程,后台执行该await表达式,实现异步。

第1个await表达式,未完成以前,继续执行调用函数中的异步方法以后的代码(注意await表达式后台未完成以前,不是继续执行await表达式以后的代码,而是继续执行调用函数中的异步方法以后的代码),

当第1个await表达式在后台完成后,继续执行异步方法中第1个await表达式以后的代码,

当运行到第2个await表达式后,建立一个新的线程,后台运行该await表达式,

第2个await表达式,未完成以前,继续执行调用函数中被第1个await完成后打断的的代码

当第2个await表达式在后台运行完成后,继续执行异步方法中第2个await表达式以后的代码,

当异步方法运行到return后,则开始调用方法中的对该异步方法的延续任务

该延续任务和调用方法不在一个线程中,这里有可能和第2个await表达式在同一个线程中,也有可能和第1个await表达式在同一个线程中。




3.取消一个异步操做

具体可参考:.net异步编程值任务并行库-3.6取消异步操做

原理是同样的,都是使用CancellationTokenCancellationTokenSource两个类实现取消异步操做

看一个简单的示例:

static void Main(string[] args)
        {
            CancellationTokenSource cts = new CancellationTokenSource();//生成一个CancellationTokenSource对象,
            CancellationToken ct = cts.Token;//该对象能够建立CancellationToken对象,即一个令牌(token)
            Task result = DoAsync(ct, 50);
            for (int i = 0; i <= 5; i++)//主线程中的循环(模拟在异步方法声明以后的工做)
            {
                Thread.Sleep(1000);
                Console.WriteLine("主线程中的循环次数:" + i);
            }
            cts.Cancel();//注意在主线程中的循环结束后(5s左右),运行到此处,
                         //则此时CancellationTokenSource对象中的token.IsCancellationRequested==true
                         //则在异步操做DoAsync()中根据此判断,则取消异步操做
            Console.ReadKey();
            CancellTask();

            CancellTask2();
        }

        //异步方法:取消异步操做的令牌经过参数传入
        static async Task DoAsync(CancellationToken ct, int Max)
        {
            await Task.Run(() =>
            {
                for (int i = 0; i <= Max; i++)
                {
                    if (ct.IsCancellationRequested)//一旦CancellationToken对象的源CancellationTokenSource运行了Cancel();此时CancellationToken.IsCancellationRequested==ture
                    {
                        return;
                    }
                    Thread.Sleep(1000);
                    Console.WriteLine("次线程中的循环次数:" + i);
                }
            }/*,ct*/);//这里取消令牌能够做为Task.Run()的第二个参数,也能够不写!
        }




4.同步和异步等待任务

4.1 在调用方法中同步等待任务

“调用方法能够调用任意多个异步方法并接收它们返回的Task对象。而后你的代码会继续执行其余任务,但在某个点上可能会须要等待某个特殊Task对象完成,而后再继续。为此,Task类提供了一个实例方法wait,能够在Task对象上调用该方法。”--《C#图解教程》

使用task.Wait();等待异步任务task完成:

Wait方法用于单一Task的对象。如果想要等待多个Task,可使用Task类中的两个静态方法,其中WaitAll等待全部任务都结束,WaitAny等待任一任务结束。

示例:使用Task.WaitAll()和Task.WaitAny()

static void Main(string[] args)
{
    Console.WriteLine($"当前线程ID:{Thread.CurrentThread.ManagedThreadId,2 }:Task以前...");
    Task<int> t1 = DoAsync(2000);
    Task<int> t2 = DoAsync(6000);

    //Task.WaitAll(t1, t2);//等待t1和t2都完毕后才进行后续的代码(即阻塞了主线程)
    //Task.WaitAny(t1, t2);//等待t1和t2中有任一个完成(调试的时候,你就会发现当t1完成后就开始执行后续的循代码)

    for (int i = 0; i < 10; i++)
    {
        Thread.Sleep(1000);
        Console.WriteLine($"当前线程ID:{Thread.CurrentThread.ManagedThreadId,2}:循环中");
    }
    Console.ReadKey();
}

private static async Task<int> DoAsync(int num)
{
    int result = await Task.Run(() => { Thread.Sleep(num); Console.WriteLine($"当前线程ID{Thread.CurrentThread.ManagedThreadId,2}:异步操做之等待:{num}s"); return num; });
     return result;
}

说明1 :

对代码中注释掉的代码分别调试,则能够发现其中的不一样。

Task.WaitAll(params Task[] tasks):表示中止当前主线程,等待Task类型数组tasks中的全部Task操做结束,即会发生阻塞

Task.WaitAll()调试结果:

说明2:

Task.WaitAny(params Task[] tasks):表示中止当前主线程,等待Task类型数组tasks中的任一个Task操做结束,也是会发生阻塞,单是阻塞主线程在任一个Task操做结束,以后主线程会继续,其余的Task在后台继续

Task.WaitAny()调试结果:


4.2 在调用方法中异步等待任务

4.2.1使用await等待异步任务

其实在一个方法中调用多个异步方法时候,当某个异步方法依赖于另一个异步方法的结果的时候,咱们通常是在每个调用的异步方法处使用await关键字等待该异步操做的结果,可是这样就会出现async传染。

await不一样于Wait(),await等待是异步的,不会阻塞线程,而Wait()会阻塞线程

注意如无必用,或是不存在对某个异步操做的等待,尽可能不要使用await,直接把异步操做的返回值给Task<T>类型的变量,可使程序运行的更快!

其实你也注意到了:不使用await等待异步操做,则异步操做的返回值就是定义的返回值Task<T>,可是使用await等待则异步操做的返回值就是具体的简单类型,好比int类型等。
换言之:异步方法的返回值是Task<T>,则使用await等待能够直接获取异步方法的T类型的返回值

示例:

static void Main(string[] args)
{
  ReferencingMethodAsync();
}

//该调用函数也要使用async关键字修饰(即async传染),由于使用了await等待,
private static async void ReferencingMethodAsync()
{
    int result1 = await SumAsync(1, 2);//这里使用了await 关键字则,调用方法MultipleMethod2()必须使用async修饰(即async传染性)
    int result2 = await SumAsync(1, result1);
    Console.WriteLine(result2);
}

private static async Task<int> SumAsync(int num1, int num2)
{
    int sum = await Task.Run(() => { Thread.Sleep(3000); return num1 + num2; });
    return sum;
}

4.2.2使用WhenAll()和WhenAny()

Task.WhenAll()Task.WhenAny()Task.WaitAll()Task.WaitAny()的异步版本,即异步等待Task完成

示例:使用Task.WhenAll()和Task.WhenAny()

static void Main(string[] args)
{
    Console.WriteLine($"当前线程ID:{Thread.CurrentThread.ManagedThreadId,2 }:Task以前...");
    Task<int> t1 = DoAsync(2000);
    Task<int> t2 = DoAsync(6000);
    //Task.WhenAll(t1, t2);//异步等待t1和t2两个完成(调试的时候你会发现任务t1和t2都在新的线程中执行,主线继续执行后续的循环代码)
    //Task.WhenAny(t1, t2);//异步等待t1和t2中任一个完成(调试的时候你就会发现两个任务分别在新线程中执行,线程继续执行后续的循环代码,当t1完成后,继续后续的循环代码)
       
    for (int i = 0; i < 10; i++)
    {
        Thread.Sleep(1000);
        Console.WriteLine($"当前线程ID:{Thread.CurrentThread.ManagedThreadId,2}:循环中");
    }
    Console.ReadKey();
}

private static async Task<int> DoAsync(int num)
{
    int result = await Task.Run(() => { Thread.Sleep(num); Console.WriteLine($"当前线程ID{Thread.CurrentThread.ManagedThreadId,2}:异步操做之等待:{num}s"); return num; });
    return result;
}

说明1

在示例中看到Task.WhenAll和Task.WhenAny的使用,可是在实际中有什么做用呢?

首先,如前所所述,Task.WhenAll()和Task.WhenAny()是Task.WaitAll()和Task.WaitAny()的异步版本,可是呢,Task.WaitAll()和Task.WaitAny()是没有返回值的,Task.WhenAll()Task.WhenAny()是有返回值,返回值类型是一个Task对象,因此你能够给其一个延续任务,即在异步等待的Task完成后,指定继续执行的Task。

因此当调用的异步方法没有相互的依赖的时候,通常仍是使用WhenAll(),来等待异步方法,同时也能够给全部的异步方法结束后添加一个延续任务!

示例:为异步等待后添加延续工做

static void Main(string[] args)
{
    Console.WriteLine($"当前线程ID:{Thread.CurrentThread.ManagedThreadId,2 }:Task以前...");
    Task<int> t1 = DoAsync(2000);
    Task<int> t2 = DoAsync(6000);

    //Task.WhenAll(t1, t2).ContinueWith(t => Console.WriteLine($"当前线程ID:{Thread.CurrentThread.ManagedThreadId,2 }:延续任务,两个异步操做返回值是一个int[],其中元素分别是{t.Result[0]}、{t.Result[1]}"));

    //Task.WhenAny(t1, t2).ContinueWith(t => Console.WriteLine($"当前线程ID:{Thread.CurrentThread.ManagedThreadId,2 }:延续任务,第一个完成的异步操做返回值是{t.Result.Result}"));


    for (int i = 0; i < 8; i++)
    {
        Thread.Sleep(1000);
        Console.WriteLine($"当前线程ID:{Thread.CurrentThread.ManagedThreadId,2}:循环中");
    }
    Console.ReadKey();
}

private static async Task<int> DoAsync(int num)
{
    int result = await Task.Run(() => { Thread.Sleep(num); Console.WriteLine($"当前线程ID:{Thread.CurrentThread.ManagedThreadId,2}:异步操做之等待:{num}s"); return num; });
    return result;
}

说明1

如果Task.WhenAll()后的延续工做,则注意Task.WhenAll()的返回的Task<TResult>ResultTResult[]类型
即多个Task的返回值,存放在一个数组中

运行结果:

说明2:

如果Task.WhenAny()后的延续工做,则注意Task.WhenAny()的返回的是Task<Task>类型,即其ResultTask<TResutl>类型,因此为了获取第一结束的Task的返回值,须要:t.Result.Result

运行结果:

说明3

Task.WhenAll(Task[] tasks).ContinueWith(Action<Task>)
等价于Task.Factory.ContinueWhenAll(Task[] tasks, Action<Tast>)

Task.WhenAny(Task[] tasks).ContinueWith(Action<Task>)
等价于Task.Factory.ContinueWhenAny(Task[] tasks, Action<Tast>)




5.异步操做中的异常处理

5.1 异常处理

通常程序中对异常的处理使用try{……} catch{……}

首先看一个捕获异常失败的示例:

在Main()中调用ThrowEx(2000,"这是异常信息"),第一个参数是ThrowEx中的Tast延迟的时间,第二个参数是ThrowEx中的抛出异常的信息。

static void Main(string[] args)
{
    try
    {
        ThrowEx(2000, "这是异常信息");
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
    Console.ReadKey();
}
private async static Task ThrowEx(int ms, string message)//注意这里的返回值类型为Task,如果写成void也是没法在catch语句中捕获异常,可是运行vs会报错(见:说明2)
{
    await Task.Delay(ms).ContinueWith(t => Console.WriteLine("hello word"));
    throw new Exception(message);
}

说明1:

多打断点,就能够发现为什么捕捉不到异常了。

由于当调用ThrowEx(2000, "异常信息"),开始异步方法中的await表达式,

即建立一个新的线程,在后台执行await表达式,而主线程中此时会继续执行ThrowEx(2000, "异常信息");后的代码:catch (Exception ex)

此时,异步方法中还在等待await表达式的执行,尚未抛出咱们本身定义的异常,因此此时压根就没有异常抛出,因此catch语句也就捕获不到异常,

而当异步方法抛出异常,此时主线程中catch语句已经执行完毕了!

说明2:

1.基本语法-返回值类型中咱们说道:在编写异步方法的时候,有时后没有返回值,也不须要查看异步操做的状态,咱们设置返回值类型为void,并且称之为“调用并忘记”。然而这种异步代码编写方式,并不值得提倡。

为何呢?如果没有返回值,异步方法中抛出的异常就没法传递到主线程,在主线程中的catch语句就没法捕获拍异常!因此异步方法最好返回一个Task类型

异步方法有返回值的时候,抛出的在异常会置于Task对象中,能够经过task.IsFlauted属性查看是否有异常,在主线程的调用方法中,使用catch语句能够捕获异常!


正确示例:只须要给调用的异步方法,添加一个await

static void Main(string[] args)
{
    try
    {
       await ThrowEx(2000, "这是异常信息");
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
    Console.ReadKey();
}
private async static Task ThrowEx(int ms, string message)
{
    await Task.Delay(ms).ContinueWith(t => Console.WriteLine("hello word"));
    throw new Exception(message);
}

5.2 多个异步方法的异常处理

使用Task.WhenAll()处理多个异步方法中抛出异常

当有多个异步操做,使用WhenAll异步等待,其返回值是一个Task类型对象,该对象的异常为AggregateException类型的异常,每个的子任务(即WhenAll所等待的全部任务)抛出的异常都是包装在这一个AggregateException中,如果须要打印其中的异常,则须要遍历AggregateException.InnerExceptions

static void Main(string[] args)
{
    Task taskResult = null;//注意由于在catch语句中须要使用这个WhenAll的返回值,因此定义在try语句以外。
    try
    {
        Console.WriteLine($"当前的线程Id:{Thread.CurrentThread.ManagedThreadId,2}:do something before task");
        Task t1 = ThrowEx($"这是第一个抛出的异常信息:异常所在线程ID:{Thread.CurrentThread.ManagedThreadId,2}", 3000);
        Task t2 = ThrowEx($"这是第二个抛出的异常信息:异常所在线程ID:{Thread.CurrentThread.ManagedThreadId,2}", 5000);

        await (taskResult = Task.WhenAll(t1, t2));
    }
    catch (Exception)//注意这里捕获的异常只是WhenAll()等待的异步任务中第一抛出的异常
    {
        foreach (var item in taskResult.Exception.InnerExceptions)//经过WhenAll()的返回对象的Exception属性来查阅全部的异常信息
        {
            Console.WriteLine($"当前的线程Id:{Thread.CurrentThread.ManagedThreadId,2}:{item.Message}");
        }
    }
}

private async static Task ThrowEx(int ms, string message)
{
    await Task.Delay(ms).ContinueWith(t => Console.WriteLine("hello word"));
    throw new Exception(message);
}

运行结果:

说明

Task.WhenAll()返回的Task对象中的Exception属性是AggregateException类型的异常.

注意,该访问该异常InnerExcption属性则只包含第一个异常,该异常的InnerExcptions属性,则包含全部子任务异常.

5.3 AggregateException中的方法

首先多个异步操做的异常会包装在一个AggregateException异常中,被包装的异常能够也是AggregateException类型的异常,因此如果须要打印异常信息可能须要循环嵌套,比较麻烦。

故可使用 AggregateException.Flatten()打破异常的嵌套。

注意,凡是使用await等待的异步操做,它抛出的异常没法使用catch(AggregateException)捕获!

只能使用catch (Exception)对异常捕获,在经过使用Task的返回值的Exception属性对异性进行操做。

固然你要是想使用catch(AggregateException)捕获到异常,则可使用task.Wait()方法等待异步任务,则抛出的异常为AggregateException类型的异常

示例:(完整Demo

catch (AggregateException ae)//AggregateException类型异常的错误信息是“发生一个或多个异常”
{
    foreach (var exception in ae.Flatten().InnerExceptions)
    //使用AggregateException的Flatten()方法,除去异常的嵌套,这里你也能够测试不使用Flatten(),抛出的信息为“有一个或多个异常”
    {
        if (exception is TestException)
        {
             Console.WriteLine(exception.Message);
        }
        else
        {
            throw;
        }
    }               
}

如果须要针对AggregateException中某个或是某种异常进行处理,可使用Handle()方法

Handel()的参数是一个有返回值的委托:Func<Exception,bool> predicate

示例:(完整Demo

catch (Exception)
{
    t.Exception.Handle(e =>
    {
        if (e is TestException)//若是是TestException类型的异常
        {
            Console.WriteLine(e.Message);
        }
        return e is TestException;
    });
}




6.多线程和异步的区分

不要把多线程异步两个概念混为一谈!异步是最终目的,多线程只是咱们实现异步的一种手段!

首先,使用异步和多线程均可以免线程的阻塞,可是原理是不同的。

多线程:当前线程中建立一个新的线程,当前线程线程则不会被锁定了,可是锁定新的线程执行某个操做。换句话说就是换一条线程用来代替本来会被锁定的主线程!优势就是,线程中的处理程序的运行顺序仍是从上往下的,方便理解,可是线程间的共享变量可能形成死锁的出现。

异步:异步概念是与同步的概念相对的,简单的说就是:调用者发送一个调用请求后,调用者不须要等待被被调用者返回结果而能够继续作其余的事情。实现异步通常是经过多线程,可是还能够经过多进程实现异步!

多线程和异步能够解决不一样的问题

可是首先咱们要区分当前须要长时间操做的任务是:CPU密集型仍是IO密集型,具体可参考长时间的复杂任务又分为两种

CPU Bound:使用多线程

IO Bound:使用异步




7. 在 .NET MVC中异步编程

如今的 ASP .NET MVC项目中,若使用的.net中的方法有异步版本的就尽可能使用异步的方法。

在MVC项目中异步编程能够大大的提升网站服务器的吞吐量,便可以能够大大的提升网站的同时受理的请求数量

据传,MVC网站如果异步编程则能够提升网站的同时访问量的2.6倍。

注意是提升网站的同时访问量,而不是提升网站的访问速度!

在MVC项目异步编程的的方式和在控制台中同样,使用async和await,基于任务的异步编程模式

简单的示例:同步和异步两种方式分别读取桌面1.txt文件

//同步操做
public ActionResult Index()
{
    string msg = "";
    using (StreamReader sr = new StreamReader(@"C:\Users\shanzm\Desktop\1.txt", Encoding.Default))
    {
        while (!sr.EndOfStream)
        {
            msg = sr.ReadToEnd();
        }
    }
    return Content(msg);
}


//异步操做
public async Task<ActionResult> Index2()
{
    string msg = "";
    using (StreamReader sr = new StreamReader(@"C:\Users\shanzm\Desktop\1.txt", Encoding.Default))
    {
        while (!sr.EndOfStream)
        {
            msg = await sr.ReadToEndAsync();//使用异步版本的方法
        }
    }
    return Content(msg);
}




8. 参考及示例源码

源码:点击下载

书籍:C#高级编程

书籍:精通C#

微软:基于任务的异步编程

微软:异常处理(任务并行库)

相关文章
相关标签/搜索