(10)C#异步编程

参考博客:

https://www.cnblogs.com/yswenli/p/11987377.html

https://www.cnblogs.com/wisdomqq/archive/2012/03/26/2412349.html

https://www.cnblogs.com/scy251147/archive/2012/03/03/2378477.html

https://www.cnblogs.com/zhaoshujie/p/9634136.html

https://www.cnblogs.com/zhaoshujie/p/11192036.html

1、异步编程的相关概念

1.1、 并发、并行、异步、多线程的概念

  1. 并发:对于某个系统,需要同时处理同个用户请求,多个用户请求的过程处理多个用户请求的过程就叫并发。
  2. 并行:同一时刻有多件事情同时被执行,这些指令可能执行于同一CPU的多核上,或者多个CPU上,或者多个物理主机甚至多个网络中。
  3. 异步:和同步相对,同步是顺序执行,而异步是彼此独立,在等待某个事件的过程中继续做自己的事,不要等待这一事件完成后再工作。异步和并行并不是完全互斥的,有重合的情况;两种概念只是侧重点不同,
  4. 多线程:一般指同一进程中多个线程协作运行。

2、直接使用多线程的异步方式

直接开两个线程,异步执行各自的操作。最原始的方法,不推荐直接操作线程。

public static void Main()
{   Thread writeThread
= new Thread(new ThreadStart(WriteWapper)); Thread doSomethingThread = new Thread(new ParameterizedThreadStart(DoSomethingWapper)); ClosureClass closure = new ClosureClass(); writeThread.Start(); doSomethingThread.Start(closure);//闭包对象,用于变量穿越 writeThread.Join();   doSomethingThread.Join();   Console.WriteLine(closure.Result); } //将方法包装成适于线程调用的签名 private static void WriteWapper() {   using (var fs = new FileStream("Data.dat", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, 4096))   {     fs.Write(new byte[100], 0, 100);   } } //将方法包装成适于线程调用的签名 static void DoSomethingWapper(object state) {   ClosureClass closure = state as ClosureClass;   var result = DoSomething();   if (closure != null)   {     closure.Result = result;   } } static string DoSomething() {   Thread.Sleep(2000);   return "Finished"; } //闭包辅助类,用于存储在方法间传递内部变量与参数 class ClosureClass {   //存储方法返回值   public string Result { get; set; } }

3、 基于计时器的异步方式

计时器提供了一种重复运行的异步方式,当每次计时器到期之后,就会在线程池中开启一个线程,运行对应的回调方法。Time类中一种构造函数如下

//callback:到期之后的回调方法
//state:传给回调方法的参数
//dueTime:首次被调用的时间
//period:间隔时间
public Timer(TimerCallback callback, object state, int dueTime, int period);

示例:

namespace Timers
{
   class Program
   {
      int TimesCalled = 0;

      void Display( object state )
      {
         Console.WriteLine( "{0} {1}", (string) state, ++TimesCalled );
      }

      static void Main()
      {
         Program p = new Program();

         //2000代表2s后第一次调用;1000代表每1s重复一次
         Timer myTimer = new Timer( p.Display, "Processing timer event", 2000, 1000 );
         Console.WriteLine( "Timer started." );

         Console.ReadLine();
      }
   }
}

4、基于BackgroundWorker的异步方式

4.1、 BackgroundWorker类介绍

BackgroundWorker类会新建另一个线程,在后台持续运行某项工作,并持续和主线程进行通信。BackgroundWorker类结果如下图所示:

1)四个属性

  • WorkerReportsProgress(R/W):表示后台线程的进度信息是否汇报给主线程。
  • WorkerSupportsCacllation(R/W) :表示后台线程是否支持在主线程中取消。
  • IsBusy(R):表示后端线程是否忙(是否完成)
  • CancellationPending(R):表示是否在主线程中取消了后台线程。

2)三个方法

  • RunWorkerAsync()
    (1)开始执行一个后台操作。主线程调用该方法后,将触发BackgroundWorker.DoWork事件,系统重启一个后台线程执行DoWork事件中的代码。
    (2)该方法还有一个带参数的重载方法:RunWorkerAsync(Object)。该方法允许传递一个Object类型的参数到后台操作中,并且可以通过DoWork事件的DoWorkEventArgs.Argument属性将该参数提取出来。
    (3)当BackgroundWorker的IsBusy属性为True时,调用该方法将引发InvalidOperationException异常。
  • ReportProgress(Int percentProgress) 
    (1)报告操作进度。调用该方法后,将触发BackgroundWorker. ProgressChanged事件。另外,该方法包含了一个int类型的参数percentProgress,用来表示当前异步操作所执行的进度百分比。 
    (2)该方法还有一个重载方法:ReportProgress(Int percentProgress, Object userState)。允许传递一个Object对象到 ProgressChanged事件中,并且可以通过ProgressChanged事件的ProgressChangedEventArgs.UserState属性取得参数值。 
    (3)调用该方法之前需确保WorkerReportsProgress属性值为True,否则将引发InvalidOperationException异常。
  • CancelAsync() 
    (1)请求取消当前正在执行的异步操作,调用该方法将使BackgroundWorker.CancellationPending属性设置为True,在其他地方可以读出此属性。
    (2)调用该方法之前需确保WorkerSupportsCacllation属性值为True

3)三个事件(由主线程指定)

  • DoWork 
    (1)用于承载异步操作。当调用BackgroundWorker.RunWorkerAsync()时触发。 
    (2)需要注意的是,由于DoWork事件内部的代码运行在非UI线程之上,所以在DoWork事件内部应避免于用户界面交互,而于用户界面交互的操作应放置在ProgressChanged和RunWorkerCompleted事件中。
    (3)对应委托的参数,sender:表示调用委托的对象(即BackgroundWorker对象);DoworkEventArgs.Argument:委托调用的参数;DoWorkEventArgs.Result:委托调用后的结果。DoWorkEventArgs.Cancel:主线程是否取消后台线程
    //DoWork对应委托
    public delegate void DoWorkEventHandler(object sender, DoWorkEventArgs e);
    //DoWorkEventArgs
    public class DoWorkEventArgs : CancelEventArgs
    {
      public DoWorkEventArgs(object argument);
      public object Argument { get; }
      public object Result { get; set; }
    }                
  • ProgressChanged 
    (1)当调用BackgroundWorker.ReportProgress(int percentProgress)方式时触发该事件。 
    (2)该事件的ProgressChangedEventArgs.ProgressPercentage属性可以接收来自ReportProgress方法传递的percentProgress参数值,ProgressChangedEventArgs.UserState属性可以接收来自ReportProgress方法传递的userState参数。

    //ProgressChanged 对应委托
    public delegate void ProgressChangedEventHandler(object sender, ProgressChangedEventArgs e);
    //ProgressChangedEventArgs 
    public class ProgressChangedEventArgs : EventArgs
    {
        public ProgressChangedEventArgs(int progressPercentage, object userState);
        public int ProgressPercentage { get; }
        public object UserState { get; }
     }
  • RunWorkerCompleted 
    (1)异步操作完成或取消时执行的操作,当调用DoWork事件执行完成时触发。 
    (2)该事件的RunWorkerCompletedEventArgs参数包含三个常用的属性Error,Cancelled,Result。其中,Error表示在执行异步操作期间发生的错误;Cancelled用于判断用户是否取消了异步操作;Result属性接收来自DoWork事件的DoWorkEventArgs参数的
              Result属性值,可用于传递异步操作的执行结果。

    //RunWorkerCompleted 对应委托
    public delegate void RunWorkerCompletedEventHandler(object sender, RunWorkerCompletedEventArgs e);
    //RunWorkerCompletedEventArgs 
    public class RunWorkerCompletedEventArgs : AsyncCompletedEventArgs
    {
        public RunWorkerCompletedEventArgs(object result, Exception error, bool cancelled);
        public object Result { get; }
        public object UserState { get; }
    }
    //AsyncCompletedEventArgs 
    public class AsyncCompletedEventArgs : EventArgs
    {
        public AsyncCompletedEventArgs();
        public AsyncCompletedEventArgs(Exception error, bool cancelled, object userState);
        public bool Cancelled { get; }
        public Exception Error { get; }
        public object UserState { get; }
        protected void RaiseExceptionIfNecessary();
    }

4.2 BackGround类示例

如下程序,点击按钮后,后台线程启动执行自己的操作,每隔半秒向主线程汇报进度;取消或成功执行后弹对应框。

<Window x:Class="SimpleWorker.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="150 " Width="250">
    <StackPanel>
        <ProgressBar Name="progressBar" Height="20" Width="200" Margin="10"/>
        <Button Name="btnProcess" Width="100" Click="btnProcess_Click" Margin="5">Process</Button>
        <Button Name="btnCancel" Width="100" Click="btnCancel_Click"  Margin="5">Cancel</Button>
    </StackPanel>
</Window>

 

namespace SimpleWorker
{
   public partial class MainWindow : Window
   {
      BackgroundWorker bgWorker = new BackgroundWorker();

      public MainWindow()
      {
         InitializeComponent();

         // Set BackgroundWorker properties
         bgWorker.WorkerReportsProgress      = true;
         bgWorker.WorkerSupportsCancellation = true;

         // Connect handlers to BackgroundWorker object.
         bgWorker.DoWork               += DoWork_Handler;
         bgWorker.ProgressChanged      += ProgressChanged_Handler;
         bgWorker.RunWorkerCompleted   += RunWorkerCompleted_Handler;
      }

      private void btnProcess_Click( object sender, RoutedEventArgs e )
      {
         if ( !bgWorker.IsBusy )
            bgWorker.RunWorkerAsync();
      }

      private void ProgressChanged_Handler( object sender, ProgressChangedEventArgs args )
      {
         progressBar.Value = args.ProgressPercentage;
      }

      private void DoWork_Handler( object sender, DoWorkEventArgs args )
      {
         BackgroundWorker worker = sender as BackgroundWorker;
         for ( int i = 1; i <= 10; i++ )
         {
            if ( worker.CancellationPending )
            {
               args.Cancel = true;
               break;
            }
            else
            {
               worker.ReportProgress( i * 10 );
               Thread.Sleep( 500 );
            }
         }
      }

      private void RunWorkerCompleted_Handler( object sender, RunWorkerCompletedEventArgs args )
      {
         progressBar.Value = 0;
         if ( args.Cancelled )
            MessageBox.Show( "Process was cancelled.", "Process Cancelled" );
         else
            MessageBox.Show( "Process completed normally.", "Process Completed" );
      }

      private void btnCancel_Click( object sender, RoutedEventArgs e )
      {
         bgWorker.CancelAsync();
      }
   }
}

 

5、基于BeginInvoke和EndInvoke的异步方式

 对于委托而言,如果列表容器内只有一个方法(此时称为引用方法),可以通过委托类的两个方法BenginIvoke和EndInvoke来异步执行引用方法。

  • BeginInvoke
    (1)输入参数:引用方法对应的参数(可以有ref和out参数);Callback参数;state参数(设置BeginInvoke返回参数和回调函数中IAsyncResult.AyncState);
    (2)返回参数:IAsyncResult对象(记录异步方法当前状态)
    (3)调用时,它会在线程池中获取一个独立的线程,执行对应的方法,并立即返回到主线程。
  • EndInvoke
    (1)输入参数:BeginInvoke返回的IAsyncResult对象;返回参数:引用方法的执行结果。
    (2)调用时,清理线程状态并释放资源,如果此时BeginInvoke对应的操作还未完成,则在主线程堵塞等待。
  • AsyncResult类
    (1)IsCompleted:表示引用方法是否执行完成。
    (2)AyncState:对应BeginInvoke方法的state参数
    (3)AsyncDelegate:记录委托本身。

                    

5.1 轮询和一直等待模式

delegate long MyDel( int first, int second );

class Program
{
   static long Sum( int x, int y )
   {
      Console.WriteLine( "          Inside Sum" );
      Thread.Sleep( 100 );
      return x + y;
   }

   static void Main()
   {
      MyDel del = new MyDel( Sum );

      IAsyncResult iar = del.BeginInvoke( 3, 5, null, null );     // Start async.
      Console.WriteLine( "After BeginInvoke" );
      //轮询模式,通过IsCompleted与引用方法线程一直通信,得知是否完成。
      while ( !iar.IsCompleted )
      {
         Console.WriteLine( "Not Done" );
         for ( long i = 0; i < 10000000; i++ )
            ;                                                     
      }
      //去掉上面的轮询,这里就是一直等待模式,会堵塞主线
      long result = del.EndInvoke( iar );
      Console.WriteLine( "Result: {0}", result );
   }
}

5.2 回调模式

回调模式的不同在于,在调用BeginInvoke时指定一个回调方法,引用方法结束时需在最后调用EndInvoke,此后系统就会在主线程中自动调用这个回调方法。回调方法签名如下所示;

需要特别注意的是,如果引用方法还未结束,主线程已经结束并退出了,此时异步线程永远回不来了。

回调方法的输入参数和BeginInvoke的返回参数,是同一个对象。

public delegate void AsyncCallback(IAsyncResult ar);
delegate long MyDel( int first, int second );

class Program
{
   static long Sum( int x, int y )
   {
      Console.WriteLine( "            Inside Sum" );
      Thread.Sleep( 100 );
      return x + y;
   }

   static void CallWhenDone( IAsyncResult iar )
   {
      Console.WriteLine( "            Inside CallWhenDone." );
      AsyncResult ar = (AsyncResult) iar;
      MyDel del = (MyDel) ar.AsyncDelegate;

      long result = del.EndInvoke( iar );
      Console.WriteLine( "            The result is: {0}.", result );
   }

   static void Main()
   {
      MyDel del = new MyDel( Sum );

      Console.WriteLine( "Before BeginInvoke" );
      IAsyncResult iar = del.BeginInvoke( 3, 5, CallWhenDone, null );

      Console.WriteLine( "Doing more work in Main." );
      Thread.Sleep( 500 );
      Console.WriteLine( "Done with Main. Exiting." );
   }
}

6、基于Task的异步方式

Task是基于任何的异步模式的中心组件之一,表示异步方式的单个操

class MyClass
{public async Task DoWorkAsync()
   {
      Func<int> ten = new Func<int>( ()=> 10 );
      int a = await Task.Run( ten );
      Console.WriteLine( "{0} {1} {2}", a, b, c );
   }

static void Main() { Task t = ( new MyClass() ).DoWorkAsync(); t.Wait(); } }

 1)异步方法

  1. 以aync修饰符开始;包含一个或多个await表达式(表示需异步完成的任务);
    返回类型有三种:void,Task(需异步完成的任务),Task<T>(除task外,还包含异步方法的返回值Task<T>.Result);只有Task<T>与异步方法的返回类型有关
  2. await异步处理的任务(如下示例)
    await的之前的部分:是调用异步方法时同步执行的;
    await task部分:await是指等待任务执行完成如果task任务是不重开线程执行,则await堵塞直到任务完成。如果重开线程,则主线程执行其他操作,异步线程执行task。
    await之后的部分:如果执行任务时重开线程了,在控制台程序中,则在异步线程中执行;如果在UI等程序中,则在调用线程(主线程)。如果没有重开线程,则都在主线程中执行。
  3. 当主线程用到了Task<T>.Result或Task.wait,会堵塞直到异步方法执行完成。
  4. 异步方法的Lambda表达式:加上async关键字即可,其他和普通方法的ladmbda表达式一致。
  5. 异步方法的参数可以是任意类型和数量,但不能包含out和ref参数。

示例1:task任务不重开线程的情况。通过输出结果可知,await会堵塞主线程,直至任务完成。(并没有达到异步的效果

class Program
{
    static void Main()
    {
        Console.WriteLine("MainStart:线程Id:【{0}】", Thread.CurrentThread.ManagedThreadId);
        DoRun();
        Console.WriteLine("MainEnd:线程Id:【{0}】", Thread.CurrentThread.ManagedThreadId);
        Console.ReadLine();
    }

    public async static void DoRun()
    {
        Console.WriteLine("DoRunStart:线程Id:【{0}】", Thread.CurrentThread.ManagedThreadId);
        await MethodName();
        Console.WriteLine("DoRunEnd:线程Id:【{0}】", Thread.CurrentThread.ManagedThreadId);
    }

    static Task MethodName()
    {
        Console.WriteLine("MethodNameStart:Task运行在线程id为{0}的线程上。是否是线程池中线程?:{1}",  Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
        //不会重开线程
        Thread.Sleep(2000);
        Console.WriteLine("MethodNameEnd:Task运行在线程id为{0}的线程上。是否是线程池中线程?:{1}",  Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
        return Task.FromResult<object>(null);
    }
 }//输出结果为:
//MainStart:线程Id:【1】
//DoRunStart:线程Id:【1】
//MethodNameStart:Task运行在线程id为1的线程上。是否是线程池中线程?:False
//MethodNameEnd:Task运行在线程id为1的线程上。是否是线程池中线程?:False
//DoRunEnd:线程Id:【1】
//MainEnd:线程Id:【1】

 

示例2:task任务重开线程的情况(控制台程序)。由输出结果可知:

第一个await,由于MethodName任务里重开线程了,因此会异步等待MethodName任务方法完成。主线程会继续执行。

第二个await,由于Task.Delay重开线程了,因此会异步等待Task.Delay的完成。

Task.Delay完成后,会在异步线程中执行后续语句;等MethodName完成后,会在异步线程中执行后续语句。

class Program
{
    static void Main()
    {
        Console.WriteLine("MainStart:线程Id:【{0}】", Thread.CurrentThread.ManagedThreadId);
        DoRun();
        Console.WriteLine("MainEnd:线程Id:【{0}】", Thread.CurrentThread.ManagedThreadId);
        Console.ReadLine();
    }

    public async static void DoRun()
    {
        Console.WriteLine("DoRunStart:线程Id:【{0}】", Thread.CurrentThread.ManagedThreadId);
        await MethodName();
        Console.WriteLine("DoRunEnd:线程Id:【{0}】", Thread.CurrentThread.ManagedThreadId);
    }

    static async Task MethodName()
    {
        Console.WriteLine("MethodNameStart:Task运行在线程id为{0}的线程上。是否是线程池中线程?:{1}",  Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
        //task.delay会在线程池中取一个线程执行任务。
        await Task.Delay(2000);
        Console.WriteLine("MethodNameEnd:Task运行在线程id为{0}的线程上。是否是线程池中线程?:{1}",  Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
    }
 }

//输出结果为:
//MainStart:线程Id:【1】
//DoRunStart:线程Id:【1】
//MethodNameStart:Task运行在线程id为1的线程上。是否是线程池中线程?:False
//MainEnd:线程Id:【1】
//MethodNameEnd:Task运行在线程id为4的线程上。是否是线程池中线程?:True
//DoRunEnd:线程Id:【4】

示例3:Task.Run一定会重开一个线程执行任务。

class Program
{
    static void Main()
    {
        Console.WriteLine("MainStart:线程Id:【{0}】", Thread.CurrentThread.ManagedThreadId);
        DoRun();
        Console.WriteLine("MainEnd:线程Id:【{0}】", Thread.CurrentThread.ManagedThreadId);

        Console.ReadLine();
    }

    public async static void DoRun()
    {
        Console.WriteLine("MethodNameStart:Task运行在线程id为{0}的线程上。是否是线程池中线程?:{1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
        await Task.Run(()=> Thread.Sleep(2000));
        Console.WriteLine("MethodNameEnd:Task运行在线程id为{0}的线程上。是否是线程池中线程?:{1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
    }
 }
//输出结果为:
//MainStart:线程Id:【1】
//MethodNameStart:Task运行在线程id为1的线程上。是否是线程池中线程?:False
//MainEnd:线程Id:【1】
//MethodNameEnd:Task运行在线程id为3的线程上。是否是线程池中线程?:True

2)await Task 表达式

  1. await表达式指定一个异步执行的任务(空闲对象);task实际上就是一个awaitable类型实例。
  2. task可以有两种方式获得:
    (1) .NET预定义很多返回task<T>的方法,可以直接用于await后面,如DownloadStringTaskAync方法。
    (2) 使用Task.Run创建Task或Task<T>,这种方式系统会新建线程执行task。下图为Task.Run的所有重载类型及对应委托参数。
       

3)取消异步操作

CancellationTokenSource实例调用cancel命令,会将其CancellationToken属性的IsCancellationRequested属性设置为true,拥有CancellationToken属性的方法可以实时读到IsCancellationRequested的值,并作出相应变化。

class Program
{
   static void Main()
   {
      CancellationTokenSource cts = new CancellationTokenSource();
      CancellationToken token = cts.Token;

      Task t = RunAsync( token );

      Thread.Sleep( 3000 );                            
      cts.Cancel();                                    

      t.Wait();
      Console.WriteLine( "Was Cancelled: {0}", token.IsCancellationRequested );
   }
   
   public async Task RunAsync( CancellationToken ct )
   {
      if ( ct.IsCancellationRequested )
         return;
      await Task.Run( () => CycleMethod( ct ) );
   }

   void CycleMethod( CancellationToken ct )
   {
      Console.WriteLine( "Starting CycleMethod" );
      const int max = 5;
      for ( int i=0; i < max; i++ )
      {
         // 实时读取IsCancellationRequested属性(并不复杂,类似指针的方法)
         if ( ct.IsCancellationRequested )               
            return;
         Thread.Sleep( 1000 );
         Console.WriteLine( " {0} of {1} iterations completed", i + 1, max );
      }
   }
}

4)异常处理和异步方法

如下代码,虽然在task中抛出了异常,但返回的task的Status为RanToCompletion;IsFaulted为false;这是因为task抛出的异常被catch住了。

class Program
{
   static void Main( string[] args )
   {
      Task t = BadAsync();
      t.Wait();
      Console.WriteLine( "Task Status   : {0}", t.Status );
      Console.WriteLine( "Task IsFaulted: {0}", t.IsFaulted );
   }

   static async Task BadAsync()
   {
      try
      {
         await Task.Run( () => { throw new Exception(); } );
      }
      catch
      {
         Console.WriteLine( "Exception in BadAsync" );
      }
   }
}

5)在调用方法中同步的等待任务

调用方法可以调用任意多个异步方法并接收它们返回的Task对象,然后调用方法继续自己的操作,但在某个点可能需要等待某个task的完成,因此task类提供了Wait\WaitAll\WaitAny方法,堵塞调用方法,直到task完成。

class Program
{
    static void Main()
    {
        DoRun();
    }

   public static void DoRun()
   {
      Task<int> t1 = CountCharactersAsync( 1, "http://www.microsoft.com" );
      Task<int> t2 = CountCharactersAsync( 2, "http://www.illustratedcsharp.com" );

      Task<int>[] tasks = new Task<int>[] { t1, t2 };
      //等到所有异步任务完成 
      Task.WaitAll( tasks );
      //等到任一异步任务完成 
      //Task.WaitAny(tasks);

      Console.WriteLine( "Task 1: {0}Finished", t1.IsCompleted ? "" : "Not " );
      Console.WriteLine( "Task 2: {0}Finished", t2.IsCompleted ? "" : "Not " );
      //用到了t1.Result,会堵塞直到异步方法执行完成,等同于t1.Wait()
      Console.WriteLine("DoRun: Result = {0}", t1.Result);

    }

   private static async Task<int> CountCharactersAsync( int id, string site )
   {
      WebClient wc = new WebClient();
      string result = await wc.DownloadStringTaskAsync( new Uri( site ) );
      return result.Length;
   }
}

6)在异步方法中异步的等待任务

在异步方法中有多个异步task时,有时也需等待Task完成,可以用Task.WhenAll和Task.WhenAny方法

class Program
{
    static void Main()
    {
        DoRun();
    }

    public static void DoRun()
    {
        Task<int> t = CountCharactersAsync("http://www.microsoft.com", "http://www.illustratedcsharp.com");

        //用到了t.IsCompleted,不会堵塞。
        Console.WriteLine("DoRun: Task {0}Finished", t.IsCompleted ? "" : "Not ");
        //用到了t.Result,会堵塞直到异步方法执行完成。等同于t.Wait()
        Console.WriteLine("DoRun: Result = {0}", t.Result);

    }

    private static async Task<int> CountCharactersAsync(string site1, string site2)
    {
        WebClient wc1 = new WebClient();
        WebClient wc2 = new WebClient();
        Task<string> t1 = wc1.DownloadStringTaskAsync(new Uri(site1));
        Task<string> t2 = wc2.DownloadStringTaskAsync(new Uri(site2));

        List<Task<string>> tasks = new List<Task<string>>();
        tasks.Add(t1);
        tasks.Add(t2);

        //开始执行异步执行所有task,之前的代码时同步执行的。
        await Task.WhenAll(tasks);

        Console.WriteLine(" CCA: T1 {0}Finished", t1.IsCompleted ? "" : "Not ");
        Console.WriteLine(" CCA: T2 {0}Finished", t2.IsCompleted ? "" : "Not ");

        return t1.IsCompleted ? t1.Result.Length : t2.Result.Length;
    }
}

7)task类提供的其他方法

  • Task.Delay
    task.sleep会堵塞当前线程;但task.delay只是暂停对应的操作,不会堵塞线程,线程还可以做别的操作。
  • Task.Yield()
    在单线程异步情况下,当使用await task时,线程的使用权会返回给调用方法,让调用方法有机会用到线程。而task.Yield()会返回一个执行时间忽略不计的task(task对的方法直接返回,不进行任何操作),就可以完美交出线程使用权。

8)一个GUI方法实例

如下图所示,点击process按钮,程序将开始并持续更新进度条;成功完成时,弹出“Process completed normally”框;点击cancel按钮时,弹出“Process was cancelled at *”框。

 

 UI代码如下:

<Window x:Class="WpfAwait.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Process and Cancel" Height="150 " Width="250">
    <StackPanel>
        <Button Name="btnProcess" Width="100" Click="btnProcess_Click" HorizontalAlignment="Right" Margin="10,15,10,10">Process</Button>
        <Button Name="btnCancel" Width="100" Click="btnCancel_Click" HorizontalAlignment="Right" Margin="10,0">Cancel</Button>
        <ProgressBar Name="progressBar" Height="20" Width="200" Margin="10" HorizontalAlignment="Right"/>
    </StackPanel>
</Window>

后台逻辑代码如下:

namespace WpfAwait
{
   public partial class MainWindow : Window
   {
      CancellationTokenSource _cancellationTokenSource;
      public MainWindow()
      {
         InitializeComponent();
      }

      private async void btnProcess_Click( object sender, RoutedEventArgs e )
      {
         btnProcess.IsEnabled       = false;
         _cancellationTokenSource   = new CancellationTokenSource();
         CancellationToken cancellationToken = _cancellationTokenSource.Token;

         int completedPercent = 0;
         for ( int i = 0; i < 10; i++ )
         {
            if (cancellationToken.IsCancellationRequested )
               break;
            try
            {
               await Task.Delay( 500, cancellationToken);
               completedPercent = ( i + 1 ) * 10;
            }
            catch ( TaskCanceledException ex )
            {
               completedPercent = i * 10;
            }
            progressBar.Value = completedPercent;
         }

         string message = cancellationToken.IsCancellationRequested
                                    ? string.Format( "Process was cancelled at {0}%.", completedPercent )
                                    : "Process completed normally.";
         MessageBox.Show( message, "Completion Status" );

         progressBar.Value    = 0;
         btnProcess.IsEnabled = true;
         btnCancel.IsEnabled  = true;
      }

      private void btnCancel_Click( object sender, RoutedEventArgs e )
      {
         if ( !btnProcess.IsEnabled )
         {
            btnCancel.IsEnabled = false;
            _cancellationTokenSource.Cancel();
         }
      }
   }
}

7、并行计算(Parallel.For和Parallel.ForEach)

 对于循环结构中,如果每一次迭代不依赖之前的迭代,而且程序运行在多核处理器上,将不同的迭代放在不同的处理器上运行,就可以实现并行计算。Parrallel.For和Parallel.ForEach是任务并行库(Task Parallel Library)中的两个循环方法,就是为此而生的。

1)Paraller.For示例:

namespace ExampleParallelFor
{
   class Program
   {
      static void Main()
      {
         const int maxValues = 50;
         int[] squares = new int[maxValues];

         Parallel.For( 0, maxValues, i => squares[i] = i * i );

         for ( int i=0; i < maxValues; i++ )
            Console.WriteLine( "Element {0}: {1}", i, squares[i] );
      }
   }
}

2)Parallel.ForEach示例

namespace ParallelForeach1
{
   class Program
   {
      static void Main()
      {
         string[] squares = new string[]
                  { "We", "hold", "these", "truths", "to", "be", "self-evident",
                    "that", "all", "men", "are", "created", "equal"};

         Parallel.ForEach( squares,
         i => Console.WriteLine( string.Format( "{0} has {1} letters", i, i.Length ) ) );
      }
   }
} 
posted @ 2020-08-07 21:25  代码吸血虫  阅读(169)  评论(0)    收藏  举报