【亚洲必赢娱乐游戏】的变迁史,Await究竟做了什

写在前面

C# 5.0 搭载于.NET 4.5和VS2012之上。

  在学异步,有位园友推荐了《async in C#5.0》,没找到中文版,恰巧也想提高下英文,用我拙劣的英文翻译一些重要的部分,纯属娱乐,简单分享,保持学习,谨记谦虚。

  同步操作既简单又方便,我们平时都用它。但是对于某些情况,使用同步代码会严重影响程序的可响应性,通常来说就是影响程序性能。这些情况下,我们通常是采用异步编程来完成功能,这在前面也多次提及了。异步编程的核心原理也就是使用多线程/线程池和委托来完成任务的异步执行和返回,只不过在每个新的C#版本中,微软都替我们完成了更多的事,使得程序模板越来越傻瓜化了。

  如果你觉得这件事儿没意义翻译的又差,尽情的踩吧。如果你觉得值得鼓励,感谢留下你的赞,愿爱技术的园友们在今后每一次应该猛烈突破的时候,不选择知难而退。在每一次应该独立思考的时候,不选择随波逐流,应该全力以赴的时候,不选择尽力而为,不辜负每一秒存在的意义。

  .NET Framework 提供以下两种执行 I/O 绑定和计算绑定异步操作的标准模式:
1. 异步编程模型 (APM,Asynchronous Programming Model)

   转载和爬虫请注明原文链接,博客园 蜗牛 2016年6月27日。

  在该模型中异步操作由一对 Begin/End 方法(如 FileStream.BeginRead 和 Stream.EndRead)表示。
  异步编程模型是一种模式,该模式使用更少的线程去做更多的事。.NET Framework很多类实现了该模式,这些类都定义了BeginXXX和EndXXX类似的方法,比如FileStream类的BeginRead和EndRead方法。同时我们也可以自定义类来实现该模式(也就是在自定义的类中实现返回类型为IAsyncResult接口的BeginXXX方法和EndXXX方法);另外委托类型也定义了BeginInvoke和EndInvoke方法,使得委托可以异步执行。这些异步操作的背后都是线程池在支撑着,这是微软异步编程的基础架构,也是比较老的模式,不过从中我们可以清楚的了解异步操作的原理。

亚洲必赢娱乐游戏 1

  所有BeginXXX方法返回的都是实现了IAsyncResult接口的一个对象,并不是对应的同步方法所要得到的结果的。此时我们需要调用对应的EndXXX方法来结束异步操作,并向该方法传递IAsyncResult对象,EndXxx方法的返回类型就是和同步方法一样的。例如,FileStream的EndRead方法返回一个Int32来代表从文件流中实际读取的字节数。

目录

  对于访问异步操作的结果,APM提供了四种方式供开发人员选择:

第01章 异步编程介绍

-

第02章 为什么使用异步编程

在调用BeginXxx方法的线程上调用EndXXX方法来得到异步操作的结果,但是这种方式会阻塞调用线程,直到操作完成之后调用线程才继续运行

第03章 手动编写异步代码

查询IAsyncResult的AsyncWaitHandle属性,从而得到WaitHandle,然后再调用它的WaitOne方法来使一个线程阻塞并等待操作完成再调用EndXxx方法来获得操作的结果。

循环查询IAsyncResult的IsComplete属性,操作完成后再调用EndXxx方法来获得操作返回的结果。

  • 使用 AsyncCallback委托来指定操作完成时要调用的方法,在操作完成后调用的方法中调用EndXxx操作来获得异步操作的结果。
      在上面的4种方式中,第4种方式是APM的首选方式,因为此时不会阻塞执行BeginXxx方法的线程,然而其他三种都会阻塞调用线程,相当于效果和使用同步方法是一样,在实际异步编程中都是使用委托的方式。

看一个简答的例子:

using System;
using System.Net;
using System.Threading;

class Program
{
    static DateTime start;
    static void Main(string[] args)
    {
        // 用百度分别检索0,1,2,3,4,共检索5次
        start = DateTime.Now;
        string strReq = "http://www.baidu.com/s?wd={0}";
        for (int i = 0; i < 5; i  )
        {
            var req = WebRequest.Create(string.Format(strReq, i));
            // 注意这里的BeginGetResponse就是异步方法
            var res = req.BeginGetResponse(ProcessWebResponse, req);
        }

        Thread.Sleep(1000000);
    }

    private static void ProcessWebResponse(IAsyncResult result)
    {
        var req = (WebRequest)result.AsyncState;
        string strReq = req.RequestUri.AbsoluteUri;
        using (var res = req.EndGetResponse(result))
        {
            Console.Write("检索 {0} 的结果已经返回!t", strReq.Substring(strReq.Length - 1));
            Console.WriteLine("耗用时间:{0}毫秒", TimeSpan.FromTicks(DateTime.Now.Ticks - start.Ticks).TotalMilliseconds);
        }
    }
}

结构相当简单,使用了回调函数获取结果,就不多说了。

 

2. 基于事件的异步模式 (EAP,Event based Asynchronous programming Model)

  在该模式中异步操作由名为“XXXAsync”和“XXXCompleted”的方法/事件表示,例如WebClient.DownloadStringAsync 和 WebClient.DownloadStringCompleted,还有像常用的BackgroundWorker.RunWorkerAsync和BackgroundWorker.RunWorkerCompleted方法。
  EAP 是在 .NET Framework 2.0 版中引入的。使用陈旧的BeginXXX和EndXXX方法无疑是不够优雅的,并且程序员需要写更多的代码,特别是在UI程序中使用不太方便。UI的各种操作基本都是基于事件的,而且通常来说UI线程和子线程之间还需要互相交流,比如说显示进度,警告,相关的消息等等,直接在子线程中访问UI线程上的空间是需要写一些同步代码的。这些操作使用APM处理起来都比较麻烦,而EAP则很好的解决了这些问题,EAP里面最出色的代表就应该是BackgroundWorker类了。
  看一个网上一位仁兄写的下载的小例子:

private void btnDownload_Click(object sender, EventArgs e)
{
    if (bgWorkerFileDownload.IsBusy != true)
    {
       // 开始异步执行DoWork中指定的任务 
       bgWorkerFileDownload.RunWorkerAsync();

       // 创建RequestState对象
       requestState = new RequestState(downloadPath);
       requestState.filestream.Seek(DownloadSize, SeekOrigin.Begin);
       this.btnDownload.Enabled = false;
       this.btnPause.Enabled = true;
    }
    else
    {
       MessageBox.Show("正在执行操作,请稍后");
    }
}

private void btnPause_Click(object sender, EventArgs e)
{
  // 暂停的标准处理方式:先判断标识,然后异步申请暂停
  if (bgWorkerFileDownload.IsBusy && bgWorkerFileDownload.WorkerSupportsCancellation == true)
  {
    bgWorkerFileDownload.CancelAsync();
  }
}

// 指定Worker的工作任务,当RunWorkerAsync方法被调用时开始工作
// 这是在子线程中执行的,不允许访问UI上的元素
private void bgWorkerFileDownload_DoWork(object sender, DoWorkEventArgs e)
{
    // 获取事件源
    BackgroundWorker bgworker = sender as BackgroundWorker;

    // 开始下载
    HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(txbUrl.Text.Trim());

    // 断点续传的功能
    if (DownloadSize != 0)
    {
      myHttpWebRequest.AddRange(DownloadSize);
    }

    requestState.request = myHttpWebRequest;
    requestState.response = (HttpWebResponse)myHttpWebRequest.GetResponse();
    requestState.streamResponse = requestState.response.GetResponseStream();
    int readSize = 0;
    // 前面讲过的异步取消中子线程的工作:循环并判断标识
    while (true)
    {
      if (bgworker.CancellationPending == true)
      {
        e.Cancel = true;
        break;
      }

      readSize = requestState.streamResponse.Read(requestState.BufferRead, 0, requestState.BufferRead.Length);
      if (readSize > 0)
      {
        DownloadSize  = readSize;
        int percentComplete = (int)((float)DownloadSize / (float)totalSize * 100);
        requestState.filestream.Write(requestState.BufferRead, 0, readSize);

        // 报告进度,引发ProgressChanged事件的发生
        bgworker.ReportProgress(percentComplete);
      }
      else
      {
        break;
      }
    }
}

// 当Worker执行ReportProgress时回调此函数。此函数在UI线程中执行更新操作进度的任务
// 因为是在在主线程中工作的,可以与UI上的元素交互
private void bgWorkerFileDownload_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    this.progressBar1.Value = e.ProgressPercentage;
}

// 当Worker结束时触发的回调函数:也许是成功完成的,或是取消了,或者是抛异常了。
// 这个方法是在UI线程中执行,所以可以与UI上的元素交互
private void bgWorkerFileDownload_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Error != null)
    {
      MessageBox.Show(e.Error.Message);
      requestState.response.Close();
    }
    else if (e.Cancelled)
    {
      MessageBox.Show(String.Format("下载暂停,下载的文件地址为:{0}n 已经下载的字节数为: {1}字节", downloadPath, DownloadSize));
      requestState.response.Close();
      requestState.filestream.Close();

      this.btnDownload.Enabled = true;
      this.btnPause.Enabled = false;
    }
    else
    {
      MessageBox.Show(String.Format("下载已完成,下载的文件地址为:{0},文件的总字节数为: {1}字节", downloadPath, totalSize));

      this.btnDownload.Enabled = false;
      this.btnPause.Enabled = false;
      requestState.response.Close();
      requestState.filestream.Close();
    }
}

private void GetTotalSize()
{
    HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(txbUrl.Text.Trim());
    HttpWebResponse response = (HttpWebResponse)myHttpWebRequest.GetResponse();
    totalSize = response.ContentLength;
    response.Close();
}

// 存储申请的状态
public class RequestState
{
    public int BufferSize = 2048;

    public byte[] BufferRead;
    public HttpWebRequest request;
    public HttpWebResponse response;
    public Stream streamResponse;

    public FileStream filestream;
    public RequestState(string downloadPath)
    {
      BufferRead = new byte[BufferSize];
      request = null;
      streamResponse = null;
      filestream = new FileStream(downloadPath, FileMode.OpenOrCreate);
    }
}

  上面的例子就是实现了一个可以取消的带断点续传功能的下载器,这是个Winform程序,控件也很简单:一个Label,一个Textbox,两个Button,一个ProgressBar;把这些控件和上面的事件对应绑定即可。  

 

  在.NET 4.0 (C# 4.0)中,并行库(TPL)的加入使得异步编程更加方便快捷,在.NET 4.5 (C# 5.0)中,异步编程将更加方便。

  这里我们先回顾一下C# 4.0中的TPL的用法,看一个简单的小例子:这个例子中只有一个Button和一个Label,点击Button会调用一个函数计算一个结果,这个结果最后会显示到Label上,很简单,我们只看核心的代码:

private void button1_Click(object sender, EventArgs e)
{
    this.button1.Enabled = false;
    var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); //get UI thread context 
    var someTask = Task<int>.Factory.StartNew(() => slowFunc(1, 2)); //create and start the Task 
    someTask.ContinueWith(x =>
        {
            this.label1.Text = "Result: "   someTask.Result.ToString();
            this.button1.Enabled = true;
        }, uiScheduler
    );
}

private int slowFunc(int a, int b)
{
    System.Threading.Thread.Sleep(3000);
    return a   b;
}

  上面的slowFunc就是模拟了一个需要大量时间去运行的任务,为了不阻塞UI线程,只能使用Task去异步运行,为了在把结果显示到Label上,代码中我们使用了TaskScheduler.FromCurrentSynchronizationContext()方法同步线程上下文,使得在ContinueWith方法中可以使用UI线程上的控件,这是TPL编程中的一个常用技巧。
  说不上太麻烦,但是感觉上总之不舒服,完全没有同步代码写起来那么自然,简单。从我个人的理解来说,C# 5.0中的async和await正是提高了这方面的用户体验。
  C# 5.0中的async和await特性并没有在IL层面增加了新的成员,所以也可以说是一种语法糖。下面先看看再C# 5.0中如何解决这个问题: 

private async void button1_Click(object sender, EventArgs e)
{
    this.button1.Enabled = false;
    var someTask = Task<int>.Factory.StartNew(() => slowFunc(1, 2));
    await someTask;
    this.label1.Text = "Result: "   someTask.Result.ToString();
    this.button1.Enabled = true;
}

  注意这段代码中的async和await的用法。除了这个事件处理函数,其他的都没有变化。是不是很神奇,完全和同步代码没什么太大的区别,很是简单优雅,完全是同步方式的异步编程
  下面我们就详细的讨论一下async和await这两个关键字。

async和await
  通过使用async修饰符,可将方法、lambda表达式或匿名方法指定为异步。 使用了这个修饰符的方法或表达式,则其称为异步方法,如上面的button1_Click方法就是一个异步方法。
  异步方法提供了一种简便方式来完成可能需要长时间运行的工作,而不必阻塞调用方的线程。 异步方法的调用方(这里就是button1_Click的调用者)可以继续工作,而不必等待异步方法button1_Click完成。 完成这个特性需要使用 await 关键字,以便立即返回,从而允许button1_Click的调用方继续工作或返回到线程的同步上下文(或消息泵)。
  从上面的描述中得到,异步方法更准确的定义应该是:使用async修饰符定义的,且通常包含一个或多个await表达式的方法称为异步方法
  如果async关键字修饰的方法不包含await表达式或语句,则该方法仍将同步执行。 对于这种情况,编译器将会给出警告,因为该情况通常表示程序可能存在错误。 也就是说,单单使用async修饰符的方法还是在同步执行的,只有配合await关键字后方法的部分才开始异步执行。

  await表达式不阻塞主线程。 相反,它告诉编译器去重写异步方法来完成下面几件事:
1. 启动子线程(通常是线程池中的线程)完成await表达式中指定的任务,这是异步执行的真正含义。
2. 将await表达式后面未执行的语句注册为await表达式中执行的任务的后续任务,然后挂起这个异步方法,直接返回到异步方法的调用方。

  1. 当await表达式中执行的任务完成后,子线程结束。
    4. 任务寻找到注册的后续任务,恢复异步方法的执行环境,继续执行后续任务,因为已经恢复到异步方法的执行上下文中,所以不存在跨线程的问题。
      看了这个过程,其实与我们使用ContinueWith的那种方式没什么太大的不同。回到上面的button1_Click方法,这下就好理解了,该方法从开始时同步运行,直至到达其第一个await表达式,此时异步的执行Task中指定的方法,然后将button1_Click方法挂起,回到button1_Click的调用者执行其他的代码;直到等待的任务完成后,回到button1_Click中继续执行后续的代码,也就是更新Label的内容。

  这里需要注意几点:

  1. async和await只是上下文关键字。 当它们不修饰方法、lambda 表达式或匿名方法时,就不是关键字了,只作为普通的标识符。
  2. 使用async修饰的异步方法的返回类型可以为 Task、Task<TResult> 或 void。 方法不能声明任何 ref 或 out 参数,但是可以调用具有这类参数的方法。
      如果异步方法需要一个 TResult 类型的返回值,则需要应指定 Task<TResult> 作为方法的返回类型。
      如果当方法完成时未返回有意义的值,则应使用 Task。 对于返回Task的异步方法,当 Task 完成时,任何等待 Task 的所有 await 表达式的计算结果都为 void。
      而使用void作为返回类型的方式主要是来定义事件处理程序,这些处理程序需要此返回类型。 使用void 作为异步方法的返回值时,该异步方法的调用方不能等待,并且无法捕获该方法引发的异常。
  3. await表达式的返回值
      如果 await 应用于返回Task<TResult>的方法调用的结果,那么 await 表达式的类型是 TResult。 如果将 await 应用于返回Task的方法调用结果,则 await 表达式的类型无效。看下面的例子中的使用方式:

    // 返回Task的方法. TResult result = await AsyncMethodThatReturnsTaskTResult();

    // 返回一个Task的方法. await AsyncMethodThatReturnsTask();

4.异常问题
  大多数异步方法返回 Task 或 Task<TResult>。 返回任务的属性承载有关其状态和历史记录的信息,例如任务是否已完成,异步方法是否引发异常或已取消,以及最终结果如何。 await 运算符会访问那些属性。
  如果任务返回异常,await 运算符会再次引发异常。
  如果任务被取消后返回,await 运算符也会再次引发 OperationCanceledException。
  总之,在await外围使用try/catch可以捕获任务中的异常。看一个例子:

public class AsyncTest
{
    static void Main(string[] args)
    {
        AsyncTest c = new AsyncTest();
        c.RunAsync();

        // 模拟其他的工作
        Thread.Sleep(1000000);
    }

    public void RunAsync()
    {
        DisplayValue(); 
        //这里不会阻塞
        Console.WriteLine("RunAsync() End.");
    }

    public Task<double> GetValueAsync(double num1, double num2)
    {
        return Task.Run(() =>
        {
            for (int i = 0; i < 1000000; i  )
            {
                num1 = num1 / num2;

                if (i == 999999)
                {
                    throw new Exception("Crash");
                }
            }

            return num1;
        });
    }

    public async void DisplayValue()
    {
        double result = 0;
        //此处会开新线程处理GetValueAsync任务,然后方法马上返回
        try
        {
            result = await GetValueAsync(1234.5, 1.0);
        }
        catch (Exception)
        {
            //throw;
        }

        //这之后的所有代码都会被封装成委托,在GetValueAsync任务完成时调用
        Console.WriteLine("Value is : "   result);
    }
}

  但是需要注意一点,如果任务抛出了多个异常(例如,该任务可能是启动了更多的子线程)时,await运算符只能抛出异常中的一个,而且不能确定是哪一个。这时就需要把这些子线程包装到一个Task中,这样这些异常就都会被包装到AggregateException中,看下面例子的做法:

public class AsyncTest
{
    static void Main(string[] args)
    {
        AsyncTest c = new AsyncTest();
        c.RunAsync();

        // 模拟其他的工作
        Thread.Sleep(1000000);
    }

    public void RunAsync()
    {
        DisplayValue(); 
        //这里不会阻塞
        Console.WriteLine("RunAsync() End.");
    }

    public async void DisplayValue()
    {
        Task all = null;
        try
        {
            await (all = Task.WhenAll(
                Task.Run(() => { throw new Exception("Ex1"); }), 
                Task.Run(() => { throw new Exception("Ex2"); }))
                );
        }
        catch
        {
            foreach (var ex in all.Exception.InnerExceptions)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

  当然了,大家也别忘了最后一招杀手锏:TaskScheduler.UnobservedTaskException,使用这个去捕获一些没有处理的异常。
  到此,异步方法就介绍到这里了。最后附上一位网上兄弟写的异步执行一些耗时操作的辅助类:

public static class TaskAsyncHelper
{
    /// <summary>
    /// 将一个方法function异步运行,在执行完毕时执行回调callback
    /// </summary>
    /// <param name="function">异步方法,该方法没有参数,返回类型必须是void</param>
    /// <param name="callback">异步方法执行完毕时执行的回调方法,该方法没有参数,返回类型必须是void</param>
    public static async void RunAsync(Action function, Action callback)
    {
        Func<System.Threading.Tasks.Task> taskFunc = () =>
        {
            return System.Threading.Tasks.Task.Run(() =>
            {
                function();
            });
        };
        await taskFunc();
        if (callback != null)
            callback();
    }

    /// <summary>
    /// 将一个方法function异步运行,在执行完毕时执行回调callback
    /// </summary>
    /// <typeparam name="TResult">异步方法的返回类型</typeparam>
    /// <param name="function">异步方法,该方法没有参数,返回类型必须是TResult</param>
    /// <param name="callback">异步方法执行完毕时执行的回调方法,该方法参数为TResult,返回类型必须是void</param>
    public static async void RunAsync<TResult>(Func<TResult> function, Action<TResult> callback)
    {
        Func<System.Threading.Tasks.Task<TResult>> taskFunc = () =>
        {
            return System.Threading.Tasks.Task.Run(() =>
            {
                return function();
            });
        };
        TResult rlt = await taskFunc();
        if (callback != null)
            callback(rlt);
    }
}

简单实用!

 

推荐链接:
你必须知道的异步编程:
传统异步编程指导:
使用async异步编程指导:

第04章 编写Async方法

第05章 Await究竟做了什么

第06章 以Task为基础的异步模式

第07章 异步代码的一些工具

第08章 哪个线程在运行你的代码

第09章 异步编程中的异常

第10章 并行使用异步编程

第11章 单元测试你的异步代码

第12章 ASP.NET应用中的异步编程

第13章 WinRT应用中的异步编程

第14章 编译器在底层为你的异步做了什么

第15章 异步代码的性能

await究竟做了什么?

  我们有两种角度来看待C#5.0的async功能特性,尤其是await关键字上发生了什么:

  ·作为一个语言的功能特性,他是一个供你学习的已经定义好的行为

  ·作为一个在编译时的转换,这是一个C#语法糖,为了简略之前复杂的异步代码

  这都是真的;它们就像同一枚硬币的两面。在本章,我们将会集中在第一点上来探讨异步。在第十四章我们将会从另一个角度来探讨,即更复杂的,但是提供了一些细节使debug和性能考虑更加清晰。

休眠和唤醒一个方法

   当你的程序执行遇到await关键字时,我们想要发生两件事:

   ·为了使你的代码异步,当前执行你代码的线程应该被释放。这意味着,在普通,同步的角度来看,你的方法应该返回。

   ·当你await的Task完成时,你的方法应该从之前的位置继续,就像它没在早些时候被返回。

  为了做到这个行为,你的方法必须在遇到await时暂停,然后在将来的某个时刻恢复执行。

  我把这个过程当做一个休眠一台计算机的小规模情况来看(S4 sleep)。这个方法当前的状态会被存储起来(译者:状态存储起来,正如我们第二章厨房那个例子,厨师会把已放在烤箱中的食物的烹饪状态以标签的形式贴在上面),并且这个方法完全退出(厨师走了,可能去做其他事情了)。当一台计算机休眠,计算机的动态数据和运行数据被保存到磁盘,并且变得完全关闭。下面这段话和计算机休眠大概一个道理,一个正在await的方法除了用一点内存,不使用其他资源,那么可以看作这个正执行的线程已经被释放。

       进一步采取类似上一段的类比:一个阻塞型方法更像你暂停一台计算机(S3 sleep),它虽然使用较少的资源,但从根本上来讲它一直在运行着。

  在理想的情况下,我们希望编程者察觉不到这里的休眠。尽管实际上休眠和唤醒一个方法的中期执行是很复杂的,C#也将会确保你的代码被唤醒,就像什么都没发生一样。(译者:不得不赞叹微软对语法糖的封装和处理)。

方法的状态

  为了准确的弄清楚在你使用await时C#到底为我们做了多少事情,我想列出所有关于方法状态的所有我们记住和了解的细节。

  首先,你方法中本地的变量的值会被记住,包括以下值:

  ·你方法的参数

  ·在本范围内所有你定义的变量

  ·其他变量包括循环数

  ·如果你的方法非静态,那么包括this变量。这样,你类的成员变量在方法唤醒时都是可用的。

  他们都被存在.NET 垃圾回收堆(GC堆)的一个对象上。因此当你使用await时,一个消耗一些资源的对象将会被分配,但是在大多数情况下不用担心性能问题。

  C#也会记住在方法的什么位置会执行到await。这可以使用数字存储起来,用来表示await关键字在当前方法的位置。

  在关于如何使用await关键字没有什么特别的限制,例如,他们可以被用在一个长表达式上,可能包含不止一个await:

int myNum = await AlexsMethodAsync(await myTask, await StuffAsync());

  为了去记住剩余部分的表达式的状态在await某些东西时,增加了额外的条件。比如,当我们运行await StuffAsync()时,await myTask的结果需要被记住。.NET中间语言(IL)在栈上存储这种子类表达式,因此 ,这个栈就是我们await关键字需要存储的。

  最重要的是,当程序执行到第一个await关键字时,方法便返回了(译者:关于方法在遇到await时返回,建议读者从第一章拆分的两个方法来理解)。如果它不是一个async void方法,一个Task在这个时刻被返回,因此调用者可以等待我们以某种方式完成。C#也必须存储一种操作返回的Task的方式,这样当你的方法完成,这个Task也变得completed,并且执行者也可以返回到方法的异步链当中。确切的机制将会在第十四章中介绍。

上下文

  作为一个使await的过程尽量透明的部分,C#捕捉各种上下文在遇到await时,然后在恢复方法使将其恢复。

  在所有事情中最重要的还是同步上下文(synchronization context),即可以被用于恢复方法在一个特殊类型的线程上。这对于UI app尤其重要,就是那种只能在正确的线程上操作UI的(就是winform wpf之类的)。同步上下文是一个复杂的话题,第八章将会详细解释。

  其他类型的上下文也会被从当前调用的线程捕捉。他们的控制是通过一个相同名称的类来实现的,所以我将列出一些重要的上下文类型:

  ExecutionContext

  这是父级上下文,所有其他上下文都是它的一部分。这是.NET的系统功能,如Task使用其捕捉和传播上下文,但是它本身不包含什么行为。

  SecurityContext

  这是我们发现并找到通常被限制在当前线程的安全信息的地方。如果你的代码需要运行在特定的用户,你也许会,模拟或者扮演这个用户,或者ASP.NET将会帮你实现扮演。在这种情况下,模拟信息会存在SecurityContext。

  CallContext(这个东西耳熟能详吧,相信用过EF的都知道)

  这允许编程者存储他们在逻辑线程的生命周期中一直可用的数据。即使考虑到在很多情况下有不好的表现,它仍然可以避免程序中方法的参数传来传去。(译者:因为你存到callcontext里,随时都可以获取呀,不用通过传参数传来传去了)。LogicalCallContextis是一个相关的可以跨用应用程序域的。

       值得注意的是线程本地存储(TLS),它和CallContext的目标相似,但它在异步的情况下是不工作的,因为在一个耗时操作中,线程被释放掉了,并且可能被用于处理其他事情了。你的方法也许被唤醒并执行在一个不同的线程上。

  C#将会在你方法恢复(resume,这里就是单纯的“恢复”)的时候恢复(restore,我觉得这里指从内存中恢复)这些类型的上下文。恢复上下文将产生一些开销,比如,一个程序在使用模拟(之前的模拟身份之类的)的时候并大量使用async将会变得更慢一些。我建议必变.NET创建上下文的功能,除非你认为这真的有必要。

await能用在哪儿?

  await可以用在任何标记async的方法和和方法内大部分的地方,但是有一些地方你不能用await。我将解释为什么在某些情况下不允许await。

catch和finally块

  虽然在try块中使用await是完全允许的,但是他不允许在catch和finally块中使用。通常在catch和finall块中,异常依然在堆栈中未解决的状态,并且之后将会被抛出。如果await在这个时刻前使用,栈将会有所不同,并且抛出异常的行为将会变得难以定义。

  请记住替代在catch块中使用block的方法是在其后面,通过返回一个布尔值来记录操作是否抛出一个异常。示例如下:

try
{
   page = await webClient.DownloadStringTaskAsync("http://oreilly.com");
}
catch (WebException)
{
   page = await webClient.DownloadStringTaskAsync("http://oreillymirror.com");
}

   你可以以如下方式替代:

bool failed = false;
try
{
   page = await webClient.DownloadStringTaskAsync("http://oreilly.com");
}
catch (WebException)
{
   failed = true;
}
if (failed)
{
   page = await webClient.DownloadStringTaskAsync("http://oreillymirror.com");
}

  lock块

  lock是一种帮助编程人员防止其它线程和当前线程访问相同对象的方式。因为异步代码通常会释放开始执行异步的线程,并且会被回调并且发生回调在一个不确定的时间量之后,即被释放掉后和开始的线程不同(译者:即使相同的线程,它也是释放掉之后的了),所以在await上加锁没有任何意义。

   在一些情况下,保护你的对象不被并发访问是很重要的,但是在没有其他线程在await期间来访问你的对象,使用锁是没有必要的。在这些情况下,你的操作是有些冗余的,显式地锁定了两次,如下:

lock (sync)
{
    // Prepare for async operation
}
    int myNum = await AlexsMethodAsync();
lock (sync)
{
    // Use result of async operation
}

  另外,你可以使用一个类库来进行处理并发控制,比如NAct,我们将会在第十章介绍

  如果你不够幸运,你可能需要在执行异步操作时保持某种锁。这时,你就需要苦思冥想并小心谨慎,因为通常锁住异步调用资源,而不造成争用和死锁是非常困难的。也许遇到这种情况想其他办法或者重构你的程序是最好的选择。

  Linq Query表达式

  C#有一种语法帮助我们更加容易的去通过书写querys来达到过滤,排序,分组等目的。这些query可以被执行在.NET平台上或者转换成数据库操作甚至其他数据源操作。

IEnumerable<int> transformed = from x in alexsInts
where x != 9
select x   2;

  C#是在大多数位置是不允许在Query表达式中使用await关键字的。是因为这些位置会被编译成lambda表达式,正因为如此,该lambda表达式需要标记为async关键字。只是这样含蓄的lambda表达式不存在,即使如果真的这样做也会让人confuse。

  我们还是有办法,你可以写当量的表达式,通过使用Linq内部带的拓展方法。然后lambda表达式变得明了可读,继而你也就可以标记他们为async,从而使用await了。(译者:请对照上下代码来阅读)

IEnumerable<Task<int>> tasks = alexsInts
.Where(x => x != 9)
.Select(async x => await DoSomthingAsync(x)   await DoSomthingElseAsync(x));
IEnumerable<int> transformed = await Task.WhenAll(tasks);

  为了收集结果,我使用了Task.WhenAll,这是为Task集合所工作的工具,我将会在第七章介绍细节。

  不安全(unsafe)的代码

  代码被标记为unsafe的不能包含await,非安全的代码应该做到非常罕见并且应该保持方法独用和不需要异步。反正在编译器对await做转换的时候也会跳出unsafe代码。(译者:我觉得其实这里不用太在意啦,反正没写过unsafe关键字的代码)

捕获异常

  异步方法的异常捕获被微软设计的尽量和我们正常同步代码一样的。然而异步的复杂性意味着他们之间还会有些细微差别。在这里我将介绍异步如何简单的处理异常,我也将在第九章详细讲解注意事项。

  当耗时操作结束时,Task类型会有一个概念来表明成功还是失败。最简单的就是由IsFaulted属性来向外暴露,在执行过程中发生异常它的值就是true。await关键字将会察觉到这一点并且会抛出Task中包含的异常。

            如果你熟悉.NET异常机制,用也许会担心异常的堆栈跟踪在抛出异常时如何正确的保存。这在过去也许是不可能的。然而在.NET4.5中,这个限制被修改掉了,通过一个叫做ExceptionDispatchInfo的类,即一个协作异常的捕捉,抛出和正确的堆栈跟踪的类。

  异步方法也能察觉到异常。在执行异步方法期间发生任何异常,都不会被捕捉,他们会随着Task的返回而返回给调用者。当发生这种情况时,如果调用者在await这个Task,那么异常将会在此处抛出。(译者:之前有讲到异常在异步中会被传递)。在这种方式下,异常通过调用者传播,会形成一个虚拟的堆栈跟踪,完全就像它发生在同步代码中一样。

            我把它乘坐虚拟堆栈跟踪,因为堆栈是一个单线程拥有的这样的概念,并且在异步代码中,当前线程实际的堆栈和产生异常那个线程的堆栈可能是非常不同的。异常捕捉的是用户意图中的堆栈跟踪,而不是C#如何选择执行这些方法的细节。

直到被需要前异步方法都是同步的

  我之前说的,使用await只能消费(调用)异步方法。直到await结果发生,这个调用方法的语句在调用他们的线程中运行,就像同步方法一样。这非常具有现实意义,尤其是以一个同步的过程完成所有异步方法链时。(译者:当使用await的时候,的确就是按照同步的顺序来执行)

  还记得之前异步方法暂停在第一次遇到await时。即使这样,它有时候也不需要暂停,因为有时await的Task已经完成了。一个Task已经被完成的情况如下:

   ·他是被创建完成的,通过Task.FromResult工具方法。我们将会在第七章详细探讨。

   ·由没遇到async的async方法返回。

   ·它运行一个真正的异步操作,但是现在已经完成了(很可能是由于当前线程在遇到await之前已经做了某些事情)。

   ·它被一个遇到await的asunc方法返回,但是所await的这个之前就已经完成了。

  由于最后一个可能性,一些有趣的事情发生在你await一个已经完成的Task,很可能是在一个深度的异步方法链中。整个链很像完全同步的。这是因为在异步方法链中,第一个await被调用的方法总是异步链最深的一个。其他的方法到达后,最深的方法才有机会返回。( The others are only reached after the deepest method has had a chance to return synchronously.译者:按照语法来讲我的这句话貌似翻译的不正确,但是我个人觉得实际情况就是我说的这个样子。在遇到第一个await后,后面异步方法链中的await依次执行,逐个返回,最后才返回结果到最深的方法,也就是第一个方法,有高人来提出这里的见解吗?)

   你也许会怀疑为什么在第一种或第二种情况下还使用async。如果这些方法承诺一直同步的返回,你是正确的,并且这样写同步的代码效率高于异步并且没有await的过程。然后,这只是方法同步返回的情况。比如,一个方法缓存其结果到内存中,并在缓存可用的时候,结果可以被同步地返回,但是当它需要异步的网络请求。当你知道有一个好机会让你使用异步方法,在某种程度上你也许还想要方法返回Task或者Task<T>。(异步:既然方法链中有一个要异步,那么就会影响整体都使用异步)。

写在最后

  关于异步我还有很多疑惑,也是随着文章逐步明白,我也希望能快一点啊。

本文由亚洲必赢娱乐游戏发布于亚洲必赢娱乐,转载请注明出处:【亚洲必赢娱乐游戏】的变迁史,Await究竟做了什

TAG标签:
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。