c# 异步编程

原文连接:http://www.javashuo.com/article/p-dscnbtvz-me.htmlhtml

前言
C#异步编程有几种实现方式,异步方法就是其中的一种。异步方法是 C#5.0 才有的新特性,主要采用 async、await 关键字声明为异步方法,完成对方法的异步调用。C#5.0 对应的 VS 版本是 VS2012,对应的 .NET Framework 版本是 v4.5,因此须要在此基础上才支持。(不然可能报:找不到“async”修饰符所需的全部类型。目标框架版本是否不正确,或者缺乏对程序集的引用?)编程

什么是异步方法
1. 异步方法,是指在执行当前方法的同时,能够异步的去调用其余方法(异步方法),而且不会阻塞当前方法的线程。
2. 使用了 async 修饰符的方法称为异步方法,一般配合 await 运算符和 Task 异步任务一块儿使用。
1) 若是方法使用了 async 修饰符,则方法中须要包含一个以上 await 运算符,不然将以同步执行。
2) 反之,若是方法中包含一个以上 await 运算符,则必须声明为一个异步方法,即便用 async 修饰符。app

3. Task 分为两种:
1) Task,表示能够执行一个异步操做,声明以下:
public class Task : IAsyncResult, IDisposable { }
2) Task<TResult>,表示能够执行带有返回值的异步操做,声明以下:
public class Task<TResult> : Task { }框架

4. 异步方法的返回类型必须为 void、Task、Task<TResult> 中的其中一种。
1) void,表示无返回值,不关心异步方法执行后的结果,通常用于仅仅执行某一项任务,可是不关心结果的场景。
2) Task,表示异步方法将返回一个 Task 对象,该对象一般用于判断异步任务是否已经完成,可使用 taskObj.Wait() 方法等待,或者 taskObj.IsCompleted 判断。
3) Task<TResult>,表示异步方法将返回一个 Task<TResult> 对象,该对象的 Result 属性则是异步方法的执行结果,调用该属性时将阻塞当前线程(异步方法未执行完成时)。dom

概括一下:void 不关心结果;Task 只关心是否执行完成;Task<TResult> 不止关心是否执行完成,还要获取执行结果。

下面经过几个生活中比较形象的例子来理解异步方法的使用
1. 模拟扔垃圾(不关心结果,返回 void 类型)异步

/// <summary>
/// 扔垃圾
/// </summary>
public void DropLitter()
{
    Console.WriteLine("老婆开始打扫房间,线程Id为:{0}", GetThreadId());
    Console.WriteLine("垃圾满了,快去扔垃圾");
    CommandDropLitter();
    Console.WriteLine("无论他继续打扫,线程Id为:{0}", GetThreadId());
    Thread.Sleep(100);
    Console.WriteLine("老婆把房间打扫好了,线程Id为:{0}", GetThreadId());
}
 
/// <summary>
/// 通知我去扔垃圾
/// </summary>
public async void CommandDropLitter()
{
    Console.WriteLine("这时我准备去扔垃圾,线程Id为:{0}", GetThreadId());
    await Task.Run(() =>
    {
        Console.WriteLine("屁颠屁颠的去扔垃圾,线程Id为:{0}", GetThreadId());
        Thread.Sleep(1000);
    });
    Console.WriteLine("垃圾扔了还有啥吩咐,线程Id为:{0}", GetThreadId());
}

运行以上代码:async

以上代码在 CommandDropLitter() 方法上加了 async 修饰符,而且使用 await 运算符开启了一个新的 Task 去执行另外一个任务。注意:当前线程遇到 await 时,则马上跳回调用方法继续往下执行。而 Task 执行完成以后将执行 await 以后的代码,而且与 await 以前的线程不是同一个。异步编程

2.模拟打开电源开关(关心是否执行完成,返回 Task 类型)this

/// <summary>
/// 打开电源开关
/// </summary>
public void OpenMainsSwitch()
{
    Console.WriteLine("我和老婆正在看电视,线程Id为:{0}", GetThreadId());
    Console.WriteLine("忽然停电了,快去看下是否是跳闸了");
    Task task = CommandOpenMainsSwitch();
    Console.WriteLine("没电了先玩会儿手机吧,线程Id为:{0}", GetThreadId());
    Thread.Sleep(100);
    Console.WriteLine("手机也没电了只等电源打开,线程Id为:{0}", GetThreadId());
 
    //task.Wait();    //因此这里将被阻塞,直到任务完成
    //或者
    while (!task.IsCompleted) { Thread.Sleep(100); }
 
    Console.WriteLine("又有电了咱们继续看电视,线程Id为:{0}", GetThreadId());
}
 
/// <summary>
/// 通知我去打开电源开关
/// </summary>
public async Task CommandOpenMainsSwitch()
{
    Console.WriteLine("这时我准备去打开电源开关,线程Id为:{0}", GetThreadId());
    await Task.Run(() =>
    {
        Console.WriteLine("屁颠屁颠的去打开电源开关,线程Id为:{0}", GetThreadId());
        Thread.Sleep(1000);
    });
 
    Console.WriteLine("电源开关打开了,线程Id为:{0}", GetThreadId());
}

运行以上代码:spa

1) 可见,调用 Wait() 方法后,当前线程被阻塞了,直到 Task 执行完成后,当前线程才继续执行。
2) 注意:因为 CommandOpenMainsSwitch() 是一个异步方法,虽然返回类型为 Task 类型,可是在咱们代码中并无写(也不能写) return task 语句,这是为何呢?多是这种返回类型比较特殊,或者编译器自动帮咱们完成了吧!就算写也只能写 return 语句,后面不能跟对象表达式。

3. 模拟去买盐(不止关心是否执行完成,还要获取执行结果。返回 Task<TResult> 类型)

/// <summary>
/// 作饭
/// </summary>
public void CookDinner()
{
    Console.WriteLine("老婆开始作饭,线程Id为:{0}", GetThreadId());
    Console.WriteLine("哎呀,没盐了");
    Task<string> task = CommandBuySalt();
    Console.WriteLine("无论他继续炒菜,线程Id为:{0}", GetThreadId());
    Thread.Sleep(100);
    string result = task.Result;    //必需要用盐了,等我把盐回来(中止炒菜(阻塞线程))
    Console.WriteLine("用了盐炒的菜就是好吃【{0}】,线程Id为:{1}", result, GetThreadId());
    Console.WriteLine("老婆把饭作好了,线程Id为:{0}", GetThreadId());
}
 
/// <summary>
/// 通知我去买盐
/// </summary>
public async Task<string> CommandBuySalt()
{
    Console.WriteLine("这时我准备去买盐了,线程Id为:{0}", GetThreadId());
 
    string result = await Task.Run(() =>
    {
        Console.WriteLine("屁颠屁颠的去买盐,线程Id为:{0}", GetThreadId());
        Thread.Sleep(1000);
        return "盐买回来了,顺便我还买了一包烟";
 
    });
 
    Console.WriteLine("{0},线程Id为:{1}", result, GetThreadId());
 
    return result;
}

运行以上代码:

1) 以上代码 task.Result 会阻塞当前线程,与 task.Wait() 相似。
2) 注意:与前面返回类型为 Task 的 CommandOpenMainsSwitch() 方法同样,虽然 CommandBuySalt() 方法返回类型为 Task<string>,可是咱们的返回语句是 return 字符串。

其余示例
1. 在前面(模拟去买盐)的示例中,异步方法中只开启了一个 Task,若是开启多个 Task 又是什么状况,看代码:

public void AsyncTest()
{
    Console.WriteLine("AsyncTest() 方法开始执行,线程Id为:{0}", GetThreadId());
    Task task = Test1();
    Console.WriteLine("AsyncTest() 方法继续执行,线程Id为:{0}", GetThreadId());
    task.Wait();
    Console.WriteLine("AsyncTest() 方法结束执行,线程Id为:{0}", GetThreadId());
}
 
public async Task Test1()
{
    Console.WriteLine("Test1() 方法开始执行,线程Id为:{0}", GetThreadId());
    await Task.Factory.StartNew((state) =>
    {
        Console.WriteLine("Test1() 方法中的 {0} 开始执行,线程Id为:{1}", state, GetThreadId());
        Thread.Sleep(1000);
        Console.WriteLine("Test1() 方法中的 {0} 结束执行,线程Id为:{1}", state, GetThreadId());
    }, "task1");
 
    await Task.Factory.StartNew((state) =>
    {
        Console.WriteLine("Test1() 方法中的 {0} 开始执行,线程Id为:{1}", state, GetThreadId());
        Thread.Sleep(3000);
        Console.WriteLine("Test1() 方法中的 {0} 结束执行,线程Id为:{1}", state, GetThreadId());
    }, "task2");
 
    Console.WriteLine("Test1() 方法结束执行,线程Id为:{0}", GetThreadId());
}

运行以上代码:

当异步方法中有多个 await 时,会依次执行全部的 Task,只有当全部 Task 执行完成后才表示异步方法执行完成,当前线程才得以执行。

2. 一样之前面(模拟去买盐)的示例,若是发现其实家里还有盐,这是就要告诉我不用买了(取消异步操做),怎么实现?这就要借助 System.Threading.CancellationTokenSource 和 System.Threading.Tasks.CancellationToken 对象来完成。

/// <summary>
/// 作饭(买盐任务取消)
/// </summary>
public void CookDinner_CancelBuySalt()
{
    Console.WriteLine("老婆开始作饭,线程Id为:{0}", GetThreadId());
    Console.WriteLine("哎呀,没盐了");
    CancellationTokenSource source = new CancellationTokenSource();
    Task<string> task = CommandBuySalt_CancelBuySalt(source.Token);
    Console.WriteLine("无论他继续炒菜,线程Id为:{0}", GetThreadId());
    Thread.Sleep(100);
 
    string result = "家里的盐";
    if (!string.IsNullOrEmpty(result))
    {
        source.Cancel();    //传达取消请求
        Console.WriteLine("家里还有盐不用买啦,线程Id为:{0}", GetThreadId());
    }
    else
    {
        //若是已取消就不能再得到结果了(不然将抛出 System.Threading.Tasks.TaskCanceledException 异常)
        //你都叫我不要买了,我拿什么给你?
        result = task.Result;
    }
 
    Console.WriteLine("既然有盐我就继续炒菜【{0}】,线程Id为:{1}", result, GetThreadId());
    Console.WriteLine("老婆把饭作好了,线程Id为:{0}", GetThreadId());
    Console.WriteLine("最终的任务状态是:{0},已完成:{1},已取消:{2},已失败:{3}",
        task.Status, task.IsCompleted, task.IsCanceled, task.IsFaulted);
}
 
/// <summary>
/// 通知我去买盐(又告诉我不用买了)
/// </summary>
public async Task<string> CommandBuySalt_CancelBuySalt(CancellationToken token)
{
    Console.WriteLine("这时我准备去买盐了,线程Id为:{0}", GetThreadId());
 
    //已开始执行的任务不能被取消
    string result = await Task.Run(() =>
    {
        Console.WriteLine("屁颠屁颠的去买盐,线程Id为:{0}", GetThreadId());
        Thread.Sleep(1000);
    }, token).ContinueWith((t) =>  //若没有取消就继续执行
    {
        Console.WriteLine("盐已经买好了,线程Id为:{0}", GetThreadId());
        Thread.Sleep(1000);
 
        return "盐买回来了,顺便我还买了一包烟";
    }, token);
 
    Console.WriteLine("{0},线程Id为:{1}", result, GetThreadId());
 
    return result;
}

运行以上代码:

1) 刚开始我觉得调用 source.Cancel() 方法后会当即取消 Task 的执行,仔细一想也不太可能。若是须要在 Task 执行前或者执行期间完成取消操做,咱们本身写代码判断 cancellationToken.IsCancellationRequested 属性是否为 true(该属性在调用 source.Cancel() 后或者 source.CancelAfter() 方法到达指定时间后为 true),若是为 true 结束执行便可。
2) 这里所说的“传达取消请求”的意思是,每一个 Task 在执行以前都会检查 cancellationToken.IsCancellationRequested 属性是否为 true,若是为 true 则不执行 Task,并将设置 Status、IsCompleted、IsCanceled 等。
3) 因此,在 Task 的源码中有这样一段代码

if (cancellationToken.IsCancellationRequested)
{
    // Fast path for an already-canceled cancellationToken
    this.InternalCancel(false);
}

3. 乘热打铁,咱们再来看看多个 CancellationTokenSource 取消异步任务,以及注册取消后的回调委托方法,继续以(模拟去买盐)为例:

/// <summary>
/// 作饭(多个消息传达买盐任务取消)
/// </summary>
public void CookDinner_MultiCancelBuySalt()
{
    Console.WriteLine("老婆开始作饭,线程Id为:{0}", GetThreadId());
    Console.WriteLine("哎呀,没盐了");
    CancellationTokenSource source1 = new CancellationTokenSource();    //由于存在而取消
    CancellationTokenSource source2 = new CancellationTokenSource();    //由于放弃而取消
 
    CancellationTokenSource source = CancellationTokenSource.CreateLinkedTokenSource(source1.Token, source2.Token);
 
    //注册取消时的回调委托
    source1.Token.Register(() =>
    {
        Console.WriteLine("这是由于{0}因此取消,线程Id为:{1}", "家里还有盐", GetThreadId());
    });
 
    source2.Token.Register((state) =>
    {
        Console.WriteLine("这是由于{0}因此取消,线程Id为:{1}", state, GetThreadId());
    }, "不作了出去吃");
 
    source.Token.Register((state) =>
    {
        Console.WriteLine("这是由于{0}因此取消,线程Id为:{1}", state, GetThreadId());
    }, "没理由");
 
    //这里必须传递 CancellationTokenSource.CreateLinkedTokenSource() 方法返回的 Token 对象
    Task<string> task = CommandBuySalt_MultiCancelBuySalt(source.Token);
 
    Console.WriteLine("等等,好像不用买了,线程Id为:{0}", GetThreadId());
    Thread.Sleep(100);
 
    string[] results = new string[] { "家里的盐", "不作了出去吃", "没理由" };
    Random r = new Random();
    switch (r.Next(1, 4))
    {
        case 1:
            source1.Cancel();           //传达取消请求(家里有盐)
            //source1.CancelAfter(3000);  //3s后才调用取消的回调方法
            Console.WriteLine("既然有盐我就继续炒菜【{0}】,线程Id为:{1}", results[0], GetThreadId());
            break;
        case 2:
            source2.Cancel();           //传达取消请求(不作了出去吃)
            //source2.CancelAfter(3000);  //3s后才调用取消的回调方法
            Console.WriteLine("咱们出去吃不用买啦【{0}】,线程Id为:{1}", results[1], GetThreadId());
            break;
        case 3:
            source.Cancel();            //传达取消请求(没理由)
            //source.CancelAfter(3000);   //3s后才调用取消的回调方法
            Console.WriteLine("没理由就是不用买啦【{0}】,线程Id为:{1}", results[2], GetThreadId());
            break;
    }
 
    Console.WriteLine("最终的任务状态是:{0},已完成:{1},已取消:{2},已失败:{3}",
        task.Status, task.IsCompleted, task.IsCanceled, task.IsFaulted);
}
 
/// <summary>
/// 通知我去买盐(又告诉我不用买了,各类理由)
/// </summary>
public async Task<string> CommandBuySalt_MultiCancelBuySalt(CancellationToken token)
{
    Console.WriteLine("这时我准备去买盐了,线程Id为:{0}", GetThreadId());
 
    //已开始执行的任务不能被取消
    string result = await Task.Run(() =>
    {
        Console.WriteLine("屁颠屁颠的去买盐,线程Id为:{0}", GetThreadId());
        Thread.Sleep(1000);
    }, token).ContinueWith((t) =>  //若没有取消就继续执行
    {
        Console.WriteLine("盐已经买好了,线程Id为:{0}", GetThreadId());
        Thread.Sleep(1000);
        return "盐买回来了,顺便我还买了一包烟";
    }, token);
 
    Console.WriteLine("{0},线程Id为:{1}", result, GetThreadId());
 
    return result;
}

运行以上代码:

1)   当调用 source.Cancel() 方法后,会当即取消并调用 token 注册的回调方法;而调用 existSource.CancelAfter() 方法则会等到达指定的毫秒数后才会取消。
2)   注意:传递给异步方法的 token 对象,必须是 CancellationTokenSource.CreateLinkedTokenSource() 方法返回的 Token 对象,不然取消将无效。
3)   回调的委托方法始终只有两个,一个是 CancellationTokenSource.CreateLinkedTokenSource() 方法返回的 Token 对象的注册委托,另外一个是调用 Cancel()/CancelAfter() 方法的 Token 对象的注册委托。
4)   若是以上代码调用的是 CancelAfter(3000) 方法,运行结果以下:

原文连接: http://www.javashuo.com/article/p-qjheqqch-ns.html

前言
C#异步编程有几种实现方式,异步方法就是其中的一种。异步方法是 C#5.0 才有的新特性,主要采用 async、await 关键字声明为异步方法,完成对方法的异步调用。C#5.0 对应的 VS 版本是 VS2012,对应的 .NET Framework 版本是 v4.5,因此须要在此基础上才支持。(不然可能报:找不到“async”修饰符所需的全部类型。目标框架版本是否不正确,或者缺乏对程序集的引用?)

什么是异步方法
1. 异步方法,是指在执行当前方法的同时,能够异步的去调用其余方法(异步方法),而且不会阻塞当前方法的线程。
2. 使用了 async 修饰符的方法称为异步方法,一般配合 await 运算符和 Task 异步任务一块儿使用。
1) 若是方法使用了 async 修饰符,则方法中须要包含一个以上 await 运算符,不然将以同步执行。
2) 反之,若是方法中包含一个以上 await 运算符,则必须声明为一个异步方法,即便用 async 修饰符。

3. Task 分为两种:
1) Task,表示能够执行一个异步操做,声明以下:
public class Task : IAsyncResult, IDisposable { }
2) Task<TResult>,表示能够执行带有返回值的异步操做,声明以下:
public class Task<TResult> : Task { }

4. 异步方法的返回类型必须为 void、Task、Task<TResult> 中的其中一种。
1) void,表示无返回值,不关心异步方法执行后的结果,通常用于仅仅执行某一项任务,可是不关心结果的场景。
2) Task,表示异步方法将返回一个 Task 对象,该对象一般用于判断异步任务是否已经完成,可使用 taskObj.Wait() 方法等待,或者 taskObj.IsCompleted 判断。
3) Task<TResult>,表示异步方法将返回一个 Task<TResult> 对象,该对象的 Result 属性则是异步方法的执行结果,调用该属性时将阻塞当前线程(异步方法未执行完成时)。

概括一下:void 不关心结果;Task 只关心是否执行完成;Task<TResult> 不止关心是否执行完成,还要获取执行结果。

下面经过几个生活中比较形象的例子来理解异步方法的使用
1. 模拟扔垃圾(不关心结果,返回 void 类型)

  1.  
    /// <summary>
  2.  
    /// 扔垃圾
  3.  
    /// </summary>
  4.  
    public void DropLitter()
  5.  
    {
  6.  
        Console.WriteLine( "老婆开始打扫房间,线程Id为:{0}", GetThreadId());
  7.  
        Console.WriteLine( "垃圾满了,快去扔垃圾");
  8.  
        CommandDropLitter();
  9.  
        Console.WriteLine( "无论他继续打扫,线程Id为:{0}", GetThreadId());
  10.  
        Thread.Sleep( 100);
  11.  
        Console.WriteLine( "老婆把房间打扫好了,线程Id为:{0}", GetThreadId());
  12.  
    }
  13.  
     
  14.  
    /// <summary>
  15.  
    /// 通知我去扔垃圾
  16.  
    /// </summary>
  17.  
    public async void CommandDropLitter()
  18.  
    {
  19.  
        Console.WriteLine( "这时我准备去扔垃圾,线程Id为:{0}", GetThreadId());
  20.  
         await Task.Run(() =>
  21.  
        {
  22.  
            Console.WriteLine( "屁颠屁颠的去扔垃圾,线程Id为:{0}", GetThreadId());
  23.  
            Thread.Sleep( 1000);
  24.  
        });
  25.  
        Console.WriteLine( "垃圾扔了还有啥吩咐,线程Id为:{0}", GetThreadId());
  26.  
    }

运行以上代码:

以上代码在 CommandDropLitter() 方法上加了 async 修饰符,而且使用 await 运算符开启了一个新的 Task 去执行另外一个任务。注意:当前线程遇到 await 时,则马上跳回调用方法继续往下执行。而 Task 执行完成以后将执行 await 以后的代码,而且与 await 以前的线程不是同一个。

2.模拟打开电源开关(关心是否执行完成,返回 Task 类型)

  1.  
    /// <summary>
  2.  
    /// 打开电源开关
  3.  
    /// </summary>
  4.  
    public void OpenMainsSwitch()
  5.  
    {
  6.  
        Console.WriteLine( "我和老婆正在看电视,线程Id为:{0}", GetThreadId());
  7.  
        Console.WriteLine( "忽然停电了,快去看下是否是跳闸了");
  8.  
        Task task = CommandOpenMainsSwitch();
  9.  
        Console.WriteLine( "没电了先玩会儿手机吧,线程Id为:{0}", GetThreadId());
  10.  
        Thread.Sleep( 100);
  11.  
        Console.WriteLine( "手机也没电了只等电源打开,线程Id为:{0}", GetThreadId());
  12.  
     
  13.  
         //task.Wait();    //因此这里将被阻塞,直到任务完成
  14.  
         //或者
  15.  
         while (!task.IsCompleted) { Thread.Sleep( 100); }
  16.  
     
  17.  
        Console.WriteLine( "又有电了咱们继续看电视,线程Id为:{0}", GetThreadId());
  18.  
    }
  19.  
     
  20.  
    /// <summary>
  21.  
    /// 通知我去打开电源开关
  22.  
    /// </summary>
  23.  
    public async Task CommandOpenMainsSwitch()
  24.  
    {
  25.  
        Console.WriteLine( "这时我准备去打开电源开关,线程Id为:{0}", GetThreadId());
  26.  
         await Task.Run(() =>
  27.  
        {
  28.  
            Console.WriteLine( "屁颠屁颠的去打开电源开关,线程Id为:{0}", GetThreadId());
  29.  
            Thread.Sleep( 1000);
  30.  
        });
  31.  
     
  32.  
        Console.WriteLine( "电源开关打开了,线程Id为:{0}", GetThreadId());
  33.  
    }

运行以上代码:

1) 可见,调用 Wait() 方法后,当前线程被阻塞了,直到 Task 执行完成后,当前线程才继续执行。
2) 注意:因为 CommandOpenMainsSwitch() 是一个异步方法,虽然返回类型为 Task 类型,可是在咱们代码中并无写(也不能写) return task 语句,这是为何呢?多是这种返回类型比较特殊,或者编译器自动帮咱们完成了吧!就算写也只能写 return 语句,后面不能跟对象表达式。

3. 模拟去买盐(不止关心是否执行完成,还要获取执行结果。返回 Task<TResult> 类型)

  1.  
    /// <summary>
  2.  
    /// 作饭
  3.  
    /// </summary>
  4.  
    public void CookDinner()
  5.  
    {
  6.  
        Console.WriteLine( "老婆开始作饭,线程Id为:{0}", GetThreadId());
  7.  
        Console.WriteLine( "哎呀,没盐了");
  8.  
        Task< string> task = CommandBuySalt();
  9.  
        Console.WriteLine( "无论他继续炒菜,线程Id为:{0}", GetThreadId());
  10.  
        Thread.Sleep( 100);
  11.  
         string result = task.Result;     //必需要用盐了,等我把盐回来(中止炒菜(阻塞线程))
  12.  
        Console.WriteLine( "用了盐炒的菜就是好吃【{0}】,线程Id为:{1}", result, GetThreadId());
  13.  
        Console.WriteLine( "老婆把饭作好了,线程Id为:{0}", GetThreadId());
  14.  
    }
  15.  
     
  16.  
    /// <summary>
  17.  
    /// 通知我去买盐
  18.  
    /// </summary>
  19.  
    public async Task<string> CommandBuySalt()
  20.  
    {
  21.  
        Console.WriteLine( "这时我准备去买盐了,线程Id为:{0}", GetThreadId());
  22.  
     
  23.  
         string result = await Task.Run(() =>
  24.  
        {
  25.  
            Console.WriteLine( "屁颠屁颠的去买盐,线程Id为:{0}", GetThreadId());
  26.  
            Thread.Sleep( 1000);
  27.  
             return "盐买回来了,顺便我还买了一包烟";
  28.  
     
  29.  
        });
  30.  
     
  31.  
        Console.WriteLine( "{0},线程Id为:{1}", result, GetThreadId());
  32.  
     
  33.  
         return result;
  34.  
    }

运行以上代码:

1) 以上代码 task.Result 会阻塞当前线程,与 task.Wait() 相似。
2) 注意:与前面返回类型为 Task 的 CommandOpenMainsSwitch() 方法同样,虽然 CommandBuySalt() 方法返回类型为 Task<string>,可是咱们的返回语句是 return 字符串。

其余示例
1. 在前面(模拟去买盐)的示例中,异步方法中只开启了一个 Task,若是开启多个 Task 又是什么状况,看代码:

  1.  
    public void AsyncTest()
  2.  
    {
  3.  
        Console.WriteLine( "AsyncTest() 方法开始执行,线程Id为:{0}", GetThreadId());
  4.  
        Task task = Test1();
  5.  
        Console.WriteLine( "AsyncTest() 方法继续执行,线程Id为:{0}", GetThreadId());
  6.  
        task.Wait();
  7.  
        Console.WriteLine( "AsyncTest() 方法结束执行,线程Id为:{0}", GetThreadId());
  8.  
    }
  9.  
     
  10.  
    public async Task Test1()
  11.  
    {
  12.  
        Console.WriteLine( "Test1() 方法开始执行,线程Id为:{0}", GetThreadId());
  13.  
         await Task.Factory.StartNew((state) =>
  14.  
        {
  15.  
            Console.WriteLine( "Test1() 方法中的 {0} 开始执行,线程Id为:{1}", state, GetThreadId());
  16.  
            Thread.Sleep( 1000);
  17.  
            Console.WriteLine( "Test1() 方法中的 {0} 结束执行,线程Id为:{1}", state, GetThreadId());
  18.  
        }, "task1");
  19.  
     
  20.  
         await Task.Factory.StartNew((state) =>
  21.  
        {
  22.  
            Console.WriteLine( "Test1() 方法中的 {0} 开始执行,线程Id为:{1}", state, GetThreadId());
  23.  
            Thread.Sleep( 3000);
  24.  
            Console.WriteLine( "Test1() 方法中的 {0} 结束执行,线程Id为:{1}", state, GetThreadId());
  25.  
        }, "task2");
  26.  
     
  27.  
        Console.WriteLine( "Test1() 方法结束执行,线程Id为:{0}", GetThreadId());
  28.  
    }

运行以上代码:

当异步方法中有多个 await 时,会依次执行全部的 Task,只有当全部 Task 执行完成后才表示异步方法执行完成,当前线程才得以执行。

2. 一样之前面(模拟去买盐)的示例,若是发现其实家里还有盐,这是就要告诉我不用买了(取消异步操做),怎么实现?这就要借助 System.Threading.CancellationTokenSource 和 System.Threading.Tasks.CancellationToken 对象来完成。

  1.  
    /// <summary>
  2.  
    /// 作饭(买盐任务取消)
  3.  
    /// </summary>
  4.  
    public void CookDinner_CancelBuySalt()
  5.  
    {
  6.  
        Console.WriteLine( "老婆开始作饭,线程Id为:{0}", GetThreadId());
  7.  
        Console.WriteLine( "哎呀,没盐了");
  8.  
        CancellationTokenSource source = new CancellationTokenSource();
  9.  
        Task< string> task = CommandBuySalt_CancelBuySalt(source.Token);
  10.  
        Console.WriteLine( "无论他继续炒菜,线程Id为:{0}", GetThreadId());
  11.  
        Thread.Sleep( 100);
  12.  
     
  13.  
         string result = "家里的盐";
  14.  
         if (! string.IsNullOrEmpty(result))
  15.  
        {
  16.  
            source.Cancel();     //传达取消请求
  17.  
            Console.WriteLine( "家里还有盐不用买啦,线程Id为:{0}", GetThreadId());
  18.  
        }
  19.  
         else
  20.  
        {
  21.  
             //若是已取消就不能再得到结果了(不然将抛出 System.Threading.Tasks.TaskCanceledException 异常)
  22.  
             //你都叫我不要买了,我拿什么给你?
  23.  
            result = task.Result;
  24.  
        }
  25.  
     
  26.  
        Console.WriteLine( "既然有盐我就继续炒菜【{0}】,线程Id为:{1}", result, GetThreadId());
  27.  
        Console.WriteLine( "老婆把饭作好了,线程Id为:{0}", GetThreadId());
  28.  
        Console.WriteLine( "最终的任务状态是:{0},已完成:{1},已取消:{2},已失败:{3}",
  29.  
            task.Status, task.IsCompleted, task.IsCanceled, task.IsFaulted);
  30.  
    }
  31.  
     
  32.  
    /// <summary>
  33.  
    /// 通知我去买盐(又告诉我不用买了)
  34.  
    /// </summary>
  35.  
    public async Task<string> CommandBuySalt_CancelBuySalt(CancellationToken token)
  36.  
    {
  37.  
        Console.WriteLine( "这时我准备去买盐了,线程Id为:{0}", GetThreadId());
  38.  
     
  39.  
         //已开始执行的任务不能被取消
  40.  
         string result = await Task.Run(() =>
  41.  
        {
  42.  
            Console.WriteLine( "屁颠屁颠的去买盐,线程Id为:{0}", GetThreadId());
  43.  
            Thread.Sleep( 1000);
  44.  
        }, token).ContinueWith((t) =>   //若没有取消就继续执行
  45.  
        {
  46.  
            Console.WriteLine( "盐已经买好了,线程Id为:{0}", GetThreadId());
  47.  
            Thread.Sleep( 1000);
  48.  
     
  49.  
             return "盐买回来了,顺便我还买了一包烟";
  50.  
        }, token);
  51.  
     
  52.  
        Console.WriteLine( "{0},线程Id为:{1}", result, GetThreadId());
  53.  
     
  54.  
         return result;
  55.  
    }

运行以上代码:

1) 刚开始我觉得调用 source.Cancel() 方法后会当即取消 Task 的执行,仔细一想也不太可能。若是须要在 Task 执行前或者执行期间完成取消操做,咱们本身写代码判断 cancellationToken.IsCancellationRequested 属性是否为 true(该属性在调用 source.Cancel() 后或者 source.CancelAfter() 方法到达指定时间后为 true),若是为 true 结束执行便可。
2) 这里所说的“传达取消请求”的意思是,每一个 Task 在执行以前都会检查 cancellationToken.IsCancellationRequested 属性是否为 true,若是为 true 则不执行 Task,并将设置 Status、IsCompleted、IsCanceled 等。
3) 因此,在 Task 的源码中有这样一段代码

  1.  
    if (cancellationToken.IsCancellationRequested)
  2.  
    {
  3.  
        // Fast path for an already-canceled cancellationToken
  4.  
        this.InternalCancel( false);
  5.  
    }

3. 乘热打铁,咱们再来看看多个 CancellationTokenSource 取消异步任务,以及注册取消后的回调委托方法,继续以(模拟去买盐)为例:

  1.  
    /// <summary>
  2.  
    /// 作饭(多个消息传达买盐任务取消)
  3.  
    /// </summary>
  4.  
    public void CookDinner_MultiCancelBuySalt()
  5.  
    {
  6.  
        Console.WriteLine( "老婆开始作饭,线程Id为:{0}", GetThreadId());
  7.  
        Console.WriteLine( "哎呀,没盐了");
  8.  
        CancellationTokenSource source1 = new CancellationTokenSource();     //由于存在而取消
  9.  
        CancellationTokenSource source2 = new CancellationTokenSource();     //由于放弃而取消
  10.  
     
  11.  
        CancellationTokenSource source = CancellationTokenSource.CreateLinkedTokenSource(source1.Token, source2.Token);
  12.  
     
  13.  
         //注册取消时的回调委托
  14.  
        source1.Token.Register(() =>
  15.  
        {
  16.  
            Console.WriteLine( "这是由于{0}因此取消,线程Id为:{1}", "家里还有盐", GetThreadId());
  17.  
        });
  18.  
     
  19.  
        source2.Token.Register((state) =>
  20.  
        {
  21.  
            Console.WriteLine( "这是由于{0}因此取消,线程Id为:{1}", state, GetThreadId());
  22.  
        }, "不作了出去吃");
  23.  
     
  24.  
        source.Token.Register((state) =>
  25.  
        {
  26.  
            Console.WriteLine( "这是由于{0}因此取消,线程Id为:{1}", state, GetThreadId());
  27.  
        }, "没理由");
  28.  
     
  29.  
         //这里必须传递 CancellationTokenSource.CreateLinkedTokenSource() 方法返回的 Token 对象
  30.  
        Task< string> task = CommandBuySalt_MultiCancelBuySalt(source.Token);
  31.  
     
  32.  
        Console.WriteLine( "等等,好像不用买了,线程Id为:{0}", GetThreadId());
  33.  
        Thread.Sleep( 100);
  34.  
     
  35.  
         string[] results = new string[] { "家里的盐", "不作了出去吃", "没理由" };
  36.  
        Random r = new Random();
  37.  
         switch (r.Next( 1, 4))
  38.  
        {
  39.  
             case 1:
  40.  
                source1.Cancel();           //传达取消请求(家里有盐)
  41.  
                 //source1.CancelAfter(3000);  //3s后才调用取消的回调方法
  42.  
                Console.WriteLine( "既然有盐我就继续炒菜【{0}】,线程Id为:{1}", results[ 0], GetThreadId());
  43.  
                 break;
  44.  
             case 2:
  45.  
                source2.Cancel();           //传达取消请求(不作了出去吃)
  46.  
                 //source2.CancelAfter(3000);  //3s后才调用取消的回调方法
  47.  
                Console.WriteLine( "咱们出去吃不用买啦【{0}】,线程Id为:{1}", results[ 1], GetThreadId());
  48.  
                 break;
  49.  
             case 3:
  50.  
                source.Cancel();             //传达取消请求(没理由)
  51.  
                 //source.CancelAfter(3000);   //3s后才调用取消的回调方法
  52.  
                Console.WriteLine( "没理由就是不用买啦【{0}】,线程Id为:{1}", results[ 2], GetThreadId());
  53.  
                 break;
  54.  
        }
  55.  
     
  56.  
        Console.WriteLine( "最终的任务状态是:{0},已完成:{1},已取消:{2},已失败:{3}",
  57.  
            task.Status, task.IsCompleted, task.IsCanceled, task.IsFaulted);
  58.  
    }
  59.  
     
  60.  
    /// <summary>
  61.  
    /// 通知我去买盐(又告诉我不用买了,各类理由)
  62.  
    /// </summary>
  63.  
    public async Task<string> CommandBuySalt_MultiCancelBuySalt(CancellationToken token)
  64.  
    {
  65.  
        Console.WriteLine( "这时我准备去买盐了,线程Id为:{0}", GetThreadId());
  66.  
     
  67.  
         //已开始执行的任务不能被取消
  68.  
         string result = await Task.Run(() =>
  69.  
        {
  70.  
            Console.WriteLine( "屁颠屁颠的去买盐,线程Id为:{0}", GetThreadId());
  71.  
            Thread.Sleep( 1000);
  72.  
        }, token).ContinueWith((t) =>   //若没有取消就继续执行
  73.  
        {
  74.  
            Console.WriteLine( "盐已经买好了,线程Id为:{0}", GetThreadId());
  75.  
            Thread.Sleep( 1000);
  76.  
             return "盐买回来了,顺便我还买了一包烟";
  77.  
        }, token);
  78.  
     
  79.  
        Console.WriteLine( "{0},线程Id为:{1}", result, GetThreadId());
  80.  
     
  81.  
         return result;
  82.  
    }

运行以上代码:

1)   当调用 source.Cancel() 方法后,会当即取消并调用 token 注册的回调方法;而调用 existSource.CancelAfter() 方法则会等到达指定的毫秒数后才会取消。2)   注意:传递给异步方法的 token 对象,必须是 CancellationTokenSource.CreateLinkedTokenSource() 方法返回的 Token 对象,不然取消将无效。3)   回调的委托方法始终只有两个,一个是 CancellationTokenSource.CreateLinkedTokenSource() 方法返回的 Token 对象的注册委托,另外一个是调用 Cancel()/CancelAfter() 方法的 Token 对象的注册委托。4)   若是以上代码调用的是 CancelAfter(3000) 方法,运行结果以下: