《C#并发编制程序卓绝实例》笔记分分快三计划

作者:编程技术

(6)函数式OOP

  1. 异步编程是函数式的(functional),.NET 引入的async让开发者进行异步编程的时候也能用过程式编程的思维来进行思考,但是在内部实现上,异步编程仍然是函数式的

    伟人说过,世界既是过程式的,也是函数式的,但是终究是函数式的

  2. 可以用await等待的是一个类(如Task对象),而不是一个方法。可以用await等待某个方法返回的Task,无论它是不是async方法。

  3. 类的构造函数里是不能进行异步操作的,一般可以使用如下方法。相应的,我们可以通过var instance=new Program.CreateAsync();

    class Program
    {
        private Program()
        {

        }

        private async Task<Program> InitializeAsync()
        {
            await Task.Delay(TimeSpan.FromSeconds(1));

            return this;
        }

        public static Task<Program> CreateAsync()
        {
            var result = new Program();

            return result.InitializeAsync();
        }

    }
  1. 在编写异步事件处理器时,事件参数类最好是线程安全的。要做到这点,最简单的办法就 是让它成为不可变的(即把所有的属性都设为只读)

-

1.前言

最近趁着项目的一段平稳期研读了不少书籍,其中《C#并发编程经典实例》给我的印象还是比较深刻的。当然,这可能是由于近段日子看的书大多嘴炮大于实际,如《Head First设计模式》《Cracking the coding interview》等,所以陡然见到一本打着“实例”旗号的书籍,还是挺让我觉得耳目一新。本着分享和加深理解的目的,我特地整理了一些笔记(主要是Web开发中容易涉及的内容,所以部分章节如数据流,RX等我看了看就直接跳过了),以供审阅学习。语言和技术的魅力,真是不可捉摸

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

(5)集合

  1. 线程安全集合是可同时被多个线程修改的可变集合。线程安全集合混合使用了细粒度锁定和无锁技术,以确保线程被阻塞的时间最短(通常情况下是根本不阻塞)。对很多线程安全集合进行枚举操作时,内部创建了该集合的一个快照(snapshot),并对这个快照进行枚举操作。线程安全集合的主要优点是多个线程可以安全地对其进行访问,而代码只会被阻塞很短的时间,或根本不阻塞

  2. ConcurrentDictionary<TKey, TValue>是数据结构中的精品,它是线程安全的,混合使用了细粒度锁定和无锁技术,以确保绝大多数情况下能进行快速访问.

  3. ConcurrentDictionary<TKey, TValue> 内置了AddOrUpdate, TryRemove, TryGetValue等方法。如果多个线程读写一个共享集合,使用ConcurrentDictionary<TKey, TValue>是最合适的,如果不会频繁修改,那就更适合使用ImmutableDictionary<TKey, TValue>。而如果是一些线程只添加元素,一些线程只移除元素,最好使用生产者/消费者集合

查询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异步编程指导:

(2)异步编程基础

  1. 指数退避是一种重试策略,重试的延迟时间会逐 次增加。在访问 Web 服务时,最好的方式就是采用指数退避,它可以防止服务器被太多的重试阻塞
static async Task<string> DownloadStringWithRetries(string uri)
{
    using (var client = new HttpClient())
    {
        // 第 1 次重试前等 1 秒,第 2 次等 2 秒,第 3 次等 4 秒。
        var nextDelay = TimeSpan.FromSeconds(1);
        for (int i = 0; i != 3;   i)
        {
            try
            {
                return await client.GetStringAsync(uri);
            }
            catch
            { }

            await Task.Delay(nextDelay);
            nextDelay = nextDelay   nextDelay;
        }

        // 最后重试一次,以便让调用者知道出错信息。
        return await client.GetStringAsync(uri);
    }
}
  1. Task.Delay 适合用于对异步代码进行单元测试或者实现重试逻辑。要实现超时功能的话, 最好使用 CancellationToken
  2. 如何实现一个具有异步签名的同步方法。如果从异步接口或基类继承代码,但希望用同步的方法来实现它,就会出现这种情况。解决办法是可以使用 Task.FromResult 方法创建并返回一个新的 Task 对象,这个 Task 对象是已经 完成的,并有指定的值
  3. 使用 IProgress 和 Progress 类型。编写的 async 方法需要有 IProgress 参数,其 中 T 是需要报告的进度类型,可以展示操作的进度
  4. Task.WhenALl可以等待所有任务完成,而当每个Task抛出异常时,可以选择性捕获异常
  5. Task.WhenAny可以等待任一任务完成,使用它虽然可以完成超时任务(其中一个Task设为Task.Delay),但是显然用专门的带有取消标志的超时函数处理比较好
  6. 第一章提到async和上下文的问题:在默认情况下,一个 async 方法在被 await 调用后恢复运行时,会在原来的上下文中运行。而加上扩展方法ConfigureAwait(false)后,则会在await之后丢弃上下文

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

3.开发原则和要点

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

(4)测试技巧

  1. MSTest从Visual Studio2012 版本开始支持 async Task 类型的单元测试
  2. 如果单元测试框架不支持 async Task 类型的单元测试,就需要做一些额外的修改才能等待异步操作。其中一种做法是使用 Task.Wait,并在有错误时拆开 AggregateException 对象。我的建议是使用 NuGet 包 Nito.AsyncEx 中的 AsyncContext 类

这里附上一个ABP中实现的可操作AsyncHelper类,就是基于AsyncContext实现

    /// <summary>
    /// Provides some helper methods to work with async methods.
    /// </summary>
    public static class AsyncHelper
    {
        /// <summary>
        /// Checks if given method is an async method.
        /// </summary>
        /// <param name="method">A method to check</param>
        public static bool IsAsyncMethod(MethodInfo method)
        {
            return (
                method.ReturnType == typeof(Task) ||
                (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
                );
        }

        /// <summary>
        /// Runs a async method synchronously.
        /// </summary>
        /// <param name="func">A function that returns a result</param>
        /// <typeparam name="TResult">Result type</typeparam>
        /// <returns>Result of the async operation</returns>
        public static TResult RunSync<TResult>(Func<Task<TResult>> func)
        {
            return AsyncContext.Run(func);
        }

        /// <summary>
        /// Runs a async method synchronously.
        /// </summary>
        /// <param name="action">An async action</param>
        public static void RunSync(Func<Task> action)
        {
            AsyncContext.Run(action);
        }
    }
  1. 在 async 代码中,关键准则之一就是避免使用 async void。我非常建议大家在对 async void 方法做单元测试时进行代码重构,而不是使用 AsyncContext。

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

(7)同步

  1. 同步的类型主要有两种:通信和数据保护

  2. 如果下面三个条件都满足,就需要用同步来保护共享的数据

  • 多段代码正在并发运行
  • 这几段代码在访问(读或写)同一个数据
  • 《C#并发编制程序卓绝实例》笔记分分快三计划。至少有一段代码在修改(写)数据
  1. 观察以下代码,确定其同步和运行状态
class SharedData
{
    public int Value { get; set; }
}

async Task ModifyValueAsync(SharedData data)
{
    await Task.Delay(TimeSpan.FromSeconds(1));
    data.Value = data.Value   1;
}

// 警告:可能需要同步,见下面的讨论。
async Task<int> ModifyValueConcurrentlyAsync()
{
    var data = new SharedData();

    // 启动三个并发的修改过程。
    var task1 = ModifyValueAsync(data);
    var task2 = ModifyValueAsync(data);
    var task3 = ModifyValueAsync(data);

    await Task.WhenAll(task1, task2, task3);

    return data.Value;
}

本例中,启动了三个并发运行的修改过程。需要同步吗?答案是“看情况”。如果能确定 这个方法是在 GUI 或 ASP.NET 上下文中调用的(或同一时间内只允许一段代码运行的任 何其他上下文),那就不需要同步,因为这三个修改数据过程的运行时间是互不相同的。 例如,如果它在 GUI 上下文中运行,就只有一个 UI 线程可以运行这些数据修改过程,因 此一段时间内只能运行一个过程。因此,如果能够确定是“同一时间只运行一段代码”的 上下文,那就不需要同步。但是如果从线程池线程(如 Task.Run)调用这个方法,就需要同步了。在那种情况下,这三个数据修改过程会在独立的线程池线程中运行,并且同时修改 data.Value,因此必须同步地访问 data.Value。

  1. 不可变类型本身就是线程安全的,修改一个不可变集合是不可能的,即便使用多个Task.Run向集合中添加数据,也并不需要同步操作

  2. 线程安全集合(例如 ConcurrentDictionary)就完全不同了。与不可变集合不同,线程安 全集合是可以修改的。线程安全集合本身就包含了所有的同步功能

  3. 关于锁的使用,有四条重要的准则

  • 限制锁的作用范围(例如把lock语句使用的对象设为私有成员)
  • 文档中写清锁的作用内容
  • 锁范围内的代码尽量少(锁定时不要进行阻塞操作)
  • 在控制锁的时候绝不运行随意的代码(不要在语句中调用事件处理,调用虚拟方法,调用委托)
  1. 如果需要异步锁,请尝试 SemaphoreSlim

  2. 不要在 ASP. NET 中使用 Task.Run,这是因为在 ASP.NET 中,处理请求的代码本来就是在线程池线程中运行的,强行把它放到另一个线程池线程通常会适得其反

(7) 实用技巧

  1. 程序的多个部分共享了一个资源,现在要在第一次访问该资源时对它初始化
static int _simpleValue;

static readonly Lazy<Task<int>> MySharedAsyncInteger = 
    new Lazy<Task<int>>(() => 
    Task.Run(async () =>
        {
            await Task.Delay(TimeSpan.FromSeconds(2));

            return _simpleValue  ;
        }));

async Task GetSharedIntegerAsync()
{
    int sharedValue = await MySharedAsyncInteger.Value;
}

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

(3)并行开发的基础

  1. Parallel 类有一个简单的成员 Invoke,可用于需要并行调用一批方法,并且这些方法(大部分)是互相独立的
static void ProcessArray(double[] array)
{
    Parallel.Invoke(
    () => ProcessPartialArray(array, 0, array.Length / 2),
    () => ProcessPartialArray(array, array.Length / 2,array.Length));
}

static void ProcessPartialArray(double[] array, int begin, int end)
{
    // 计算密集型的处理过程 ...  
}
  1. 在并发编程中,Task类有两个作用:作为并行任务,或作为异步任务。并行任务可以使用 阻塞的成员函数,例如 Task.Wait、Task.Result、Task.WaitAll 和 Task.WaitAny。并行任务通常也使用 AttachedToParent 来建立任务之间的“父 / 子”关系。并行任务的创建需要 用 Task.Run 或者 Task.Factory.StartNew。
  2. 相反的,异步任务应该避免使用阻塞的成员函数,而应该使用 await、Task.WhenAll 和 Task. WhenAny。异步任务不使用 AttachedToParent,但可以通过 await 另一个任务,建立一种隐 式的“父 / 子”关系。

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

  • 1.前言
  • 2.开宗明义
  • 3.开发原则和要点
    • (1)并发编程概述
    • (2)异步编程基础
    • (3)并行开发的基础
    • (4)测试技巧
    • (5)集合
    • (6)函数式OOP
    • (7)同步

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

(1)并发编程概述

  1. 并发:同时做多件事情
  2. 多线程:并发的一种形式,它采用多个线程来执行程序
  3. 并行处理:把正在执行的大量的任务分割成小块,分配给多个同时运行的线程
  4. 并行处理是多线程的一种,而多线程是并发的一种处理形式
  5. 异步编程:并发的一种形式,它采用future模式或者callback机制,以避免产生不必要的线程
  6. 异步编程的核心理念是异步操作:启动了的操作会在一段时间后完成。这个操作正在执行时,不会阻塞原来的线程。启动了这个操作的线程,可以继续执行其他任务。当操作完成后,会通知它的future,或者调用回调函数,以便让程序知道操作已经结束
  7. await关键字的作用:启动一个将会被执行的Task(该Task将在新线程中运行),并立即返回,所以await所在的函数不会被阻塞。当Task完成后,继续执行await后面的代码
  8. 响应式编程:并发的一种基于声明的编程方式,程序在该模式中对事件作出反应
  9. 不要用 void 作为 async 方法的返回类型! async 方法可以返回 void,但是这仅限于编写事件处理程序。一个普通的 async 方法如果没有返回值,要返回 Task,而不是 void
  10. async 方法在开始时以同步方式执行。在 async 方法内部,await 关键字对它的参数执行一个异步等待。它首先检查操作是否已经完成,如果完成了,就继续运行 (同步方式)。否则,它会暂停 async 方法,并返回,留下一个未完成的 task。一段时间后, 操作完成,async
    方法就恢复运行。
  11. await代码中抛出异常后,异常会沿着Task方向前进到引用处
  12. 你一旦在代码中使用了异步,最好一直使用。调用 异步方法时,应该(在调用结束时)用 await 等待它返回的 task 对象。一定要避免使用 Task.Wait 或 Task.Result 方法,因为它们会导致死锁
  13. 线程是一个独立的运行单元,每个进程内部有多个线程,每个线程可以各自同时执行指令。 每个线程有自己独立的栈,但是与进程内的其他线程共享内存
  14. 每个.NET应用程序都维护着一个线程池,这种情况下,应用程序几乎不需要自行创建新的线程。你若要为 COM interop 程序创建 SAT 线程,就得 创建线程,这是唯一需要线程的情况
  15. 线程是低级别的抽象,线程池是稍微高级一点的抽象
  16. 并发编程用到的集合有两类:并发变成 不可变集合
  17. 大多数并发编程技术都有一个类似点:它们本质上都是函数式的。这里的函数式是作为一种基于函数组合的编程模式。函数式的一个编程原则是简洁(避免副作用),另一个是不变性(指一段数据不能被修改)
  18. .NET 4.0 引入了并行任务库(TPL),完全支持数据并行和任务并行。但是一些资源较少的 平台(例如手机),通常不支持 TPL。TPL 是 .NET 框架自带的

2.开宗明义

一直以来都有一种观点是实现底层架构,编写驱动和引擎,或者是框架和工具开发的才是高级开发人员,做上层应用的人仅仅是“码农”,其实能够利用好平台提供的相关类库,而不是全部采用底层技术自己实现,开发出高质量,稳定的应用程序,对技术能力的考验并不低于开发底层库,如TPL,async,await等。

本文由分分快三计划发布,转载请注明来源

关键词: 分分快三计划 基础 C# C#基础