C# 异步编程:操控Task任务(二)

在第一节中我们介绍了基于任务的异步编程,并给出了几种创建和运行异步方法的例子,在本节中,我们将讲解如何管理我们创建的任务,这些管理主要是取消、等待,延迟。

目录

1.取消一个异步操作

2.任务的同步等待

 3.任务的异步等待

4.任务延迟

5.异步Lambda表达式


1.取消一个异步操作

在前面一节的最后部分,我们给出了一个表,这个表里面的Task.Run函数有一个参数CancellatToken,这个参数就是用来决定任务是否终止的。在System.Threading命名空间里面设计了两个类:CancellaToken和CancellaTokenSource。

  • CancellationToken对象包含一个任务与是否应该被取消的信息
  • 拥有CancellationToken对象的任务需要定期检查其令牌(Token)状态,如果CancellatToken对象的IsCancellationRequest属性为True,任务需停止其操作并返回。
  • CancellationToken是不可逆的,并且只能使用一次。也就是说:一旦IsCancellationToken属性设置为true,就不能更改了。
  • CancellatioonTokenSource对象创建可分配给不同任务的CancellationToken对象。任何持有CancellationTokenSource的对象都可以调用Cancel方法,这会使Canc的IsCancelationToken的IsCancellationRequested属性设置为true。包含Cancell的代码负责检查该属性,并判断是否要停止执行并返回。

下面是一个典型的例子

using System;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;

namespace CancellationToken_Test1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            CancellationTokenSource cts = new CancellationTokenSource();
            CancellationToken token = cts.Token;
            MyClass mc = new MyClass();
            Task t = mc.RunAsync(token);

            Thread.Sleep(3000); //等待三秒
            cts.Cancel();  //取消操作

            t.Wait();
            WriteLine($"Was cancelled: {token.IsCancellationRequested}");
    
        }
    }
    class MyClass
    {
        public async Task RunAsync(CancellationToken ct)
        {
            if (ct.IsCancellationRequested)
                return;
            await Task.Run(() => CycleMethod(ct), ct);
        }
        void CycleMethod(CancellationToken ct)
        {
            WriteLine("starting CycleMethod");
            const int max = 5;
            for(int i=0;i<max;i++)
            {
                if (ct.IsCancellationRequested)
                    return;
                Thread.Sleep(1000);
                WriteLine($"{i + 1} of {max} iteration completed");
            }
        }
    }
}

当sleep(300)和cancel()没有被调用时,程序运行结果为:

Hello World!
starting CycleMethod
1 of 5 iteration completed
2 of 5 iteration completed
3 of 5 iteration completed
4 of 5 iteration completed
5 of 5 iteration completed
Was cancelled: False
请按任意键继续. . .

方法运行了5秒后结束。而当调用等待和取消函数时,结果如下:

Hello World!
starting CycleMethod
1 of 5 iteration completed
2 of 5 iteration completed
3 of 5 iteration completed
Was cancelled: True
请按任意键继续. . .

方法运行了3秒后截止了。

每一个CancellationTokenSource负责一个任务的取消。所以多多个任务,就构造多个CanncellationTokenSource对象。


2.任务的同步等待

调用方法可以调用任意多个异步方法并接收他们返回的Task对象。然后你的代码会继续执行其他任务,但在某个点上你需要等待某个特殊的Task对象完成,然后再继续,为此Task类提供了一个成员方法wait。wait的使用方法很简单,下面是一个简单的例子,调用方法DoRun调用CountCharctersAsync并接收其返回的Task<int> ,然后调用Task示例的Wait方法,等待Task结束后再显示结果.

using System;
using System.Threading.Tasks;
using System.Net;
namespace TaskWait_test
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            DownloadString.DoRun();
        }
    }
    static class DownloadString
    {
        public static void DoRun()
        {
            Task<int> t = countCahracterAsync("https://www.apple.com/cn/");
            t.Wait();  //等待任务结束
            Console.WriteLine($"The task has finished,return value {t.Result}");
        }
        private static async Task<int> countCahracterAsync(string webSite)
        {
            string result = await new WebClient().DownloadStringTaskAsync(new Uri(webSite));
            //Console.WriteLine(result);
            return result.Length;
        }
    }
}

结果如下:

Hello World!
The task has finished,return value 59865
请按任意键继续. . .

官方给的一个例子:

using System;
using System.Threading.Tasks;

public class Example
{
    public static void Main()
    {
        Task t = Task.Run(() => {
            Random rnd = new Random();
            long sum = 0;
            int n = 5000000;
            for (int ctr = 1; ctr <= n; ctr++)
            {
                int number = rnd.Next(0, 101);
                sum += number;
            }
            Console.WriteLine("Total:   {0:N0}", sum);
            Console.WriteLine("Mean:    {0:N2}", sum / n);
            Console.WriteLine("N:       {0:N0}", n);
        });
        TimeSpan ts = TimeSpan.FromMilliseconds(150);
       // if (!t.Wait(ts))
            Console.WriteLine("The timeout interval elapsed.");
    }
}
// The example displays output similar to the following:
//       Total:   50,015,714
//       Mean:    50.02
//       N:       1,000,000
// Or it displays the following output:
//      The timeout interval elapsed.

下面给出了一个运行结果(没有注释Wait(ts)的时候),如果没有等待则看不到结果,直接输出最后一行。

wait方法用于单一的Task对象,如果想对多个对象进行等待或者等待多个任务中的一个,可以用下面两个静态函数:

 

这两个方法是同步方法,且没有返回值,他们停止,直到条件满足后再继续执行,。

  • public static void WaitAll (params System.Threading.Tasks.Task[] tasks);
  • public static int WaitAny (System.Threading.Tasks.Task[] tasks, TimeSpan timeout);
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

class Example
{
    static void Main()
    {
        var tasks = new List<Task<int>>();
         
        // Define a delegate that prints and returns the system tick count
        Func<object, int> action = (object obj) =>
        {
            int i = (int)obj;

            // Make each thread sleep a different time in order to return a different tick count
            Thread.Sleep(i * 100);

            // The tasks that receive an argument between 2 and 5 throw exceptions
            if (2 <= i && i <= 5)
            {
                throw new InvalidOperationException("SIMULATED EXCEPTION");
            }

            int tickCount = Environment.TickCount;
            Console.WriteLine("Task={0}, i={1}, TickCount={2}, Thread={3}", Task.CurrentId, i, tickCount, Thread.CurrentThread.ManagedThreadId);

            return tickCount;
        };

        // Construct started tasks
        for (int i = 0; i < 10; i++)
        {
            int index = i;
            tasks.Add(Task<int>.Factory.StartNew(action, index));
        }

        try
        {
            // Wait for all the tasks to finish.
            Task.WaitAll(tasks.ToArray());

            // We should never get to this point
            Console.WriteLine("WaitAll() has not thrown exceptions. THIS WAS NOT EXPECTED.");
        }
        catch (AggregateException e)
        {
            Console.WriteLine("\nThe following exceptions have been thrown by WaitAll(): (THIS WAS EXPECTED)");
            for (int j = 0; j < e.InnerExceptions.Count; j++)
            {
                Console.WriteLine("\n-------------------------------------------------\n{0}", e.InnerExceptions[j].ToString());
            }
        }
    }
}
// The example displays output like the following:
//     Task=1, i=0, TickCount=1203822250, Thread=3
//     Task=2, i=1, TickCount=1203822359, Thread=4
//     Task=7, i=6, TickCount=1203823484, Thread=3
//     Task=8, i=7, TickCount=1203823890, Thread=4
//     Task=9, i=8, TickCount=1203824296, Thread=3
//     Task=10, i=9, TickCount=1203824796, Thread=4
//     
//     The following exceptions have been thrown by WaitAll(): (THIS WAS EXPECTED)
//     
//     -------------------------------------------------
//     System.InvalidOperationException: SIMULATED EXCEPTION
//        at Example.<Main>b__0(Object obj)
//        at System.Threading.Tasks.Task`1.InnerInvoke()
//        at System.Threading.Tasks.Task.Execute()
//     
//     -------------------------------------------------
//     System.InvalidOperationException: SIMULATED EXCEPTION
//        at Example.<Main>b__0(Object obj)
//        at System.Threading.Tasks.Task`1.InnerInvoke()
//        at System.Threading.Tasks.Task.Execute()
//     
//     -------------------------------------------------
//     System.InvalidOperationException: SIMULATED EXCEPTION
//        at Example.<Main>b__0(Object obj)
//        at System.Threading.Tasks.Task`1.InnerInvoke()
//        at System.Threading.Tasks.Task.Execute()
//     
//     -------------------------------------------------
//     System.InvalidOperationException: SIMULATED EXCEPTION
//        at Example.<Main>b__0(Object obj)
//        at System.Threading.Tasks.Task`1.InnerInvoke()
//        at System.Threading.Tasks.Task.Execute()

 上面的例子给出了WaitAll的用法和运行结果,并给出了WaitAll运行异常的情况。再看一个WaitAny的例子:

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      Task[] tasks = new Task[5];
      for (int ctr = 0; ctr <= 4; ctr++) {
         int factor = ctr;
         tasks[ctr] = Task.Run(() => Thread.Sleep(factor * 250 + 50));
      }
      int index = Task.WaitAny(tasks);
      Console.WriteLine("Wait ended because task #{0} completed.",
                        tasks[index].Id);
      Console.WriteLine("\nCurrent Status of Tasks:");
      foreach (var t in tasks)
         Console.WriteLine("   Task {0}: {1}", t.Id, t.Status);
   }
}
// The example displays output like the following:
//       Wait ended because task #1 completed.
//
//       Current Status of Tasks:
//          Task 1: RanToCompletion
//          Task 2: Running
//          Task 3: Running
//          Task 4: Running
//          Task 5: Running

因为只要有一个任务完成就会往下继续运行,所以后面的任务仍然处于等待状态。这两个方法还有很多重载版本:

 

 3.任务的异步等待

前面是同步的等待任务,但是有时候你希望用await表达式来等待任务,这时候异步方法会放回到调用方法,但该异步方法会等待一个活所有任务完成。可以通过Task.WhenAll和Task.WhenAny来实现,这两个方法称为组合子(combinator)。

两个函数模型为:

public static System.Threading.Tasks.Task WhenAll (System.Collections.Generic.IEnumerable<System.Threading.Tasks.Task> tasks);
public static System.Threading.Tasks.Task<System.Threading.Tasks.Task> WhenAny (System.Collections.Generic.IEnumerable<System.Threading.Tasks.Task> tasks);

下面给一个WhenAll的例子:

using System;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static async Task Main()
   {
      int failed = 0;
      var tasks = new List<Task>();
      String[] urls = { "www.adatum.com", "www.cohovineyard.com",
                        "www.cohowinery.com", "www.northwindtraders.com",
                        "www.contoso.com" };
      
      foreach (var value in urls) {
         var url = value;
         tasks.Add(Task.Run( () => { var png = new Ping();
                                     try {
                                        var reply = png.Send(url);
                                        if (! (reply.Status == IPStatus.Success)) {
                                           Interlocked.Increment(ref failed);
                                           throw new TimeoutException("Unable to reach " + url + ".");
                                        }
                                     }
                                     catch (PingException) {
                                        Interlocked.Increment(ref failed);
                                        throw;
                                     }
                                   }));
      }
      Task t = Task.WhenAll(tasks.ToArray());
      try {
         await t;
      }
      catch {}   

      if (t.Status == TaskStatus.RanToCompletion)
         Console.WriteLine("All ping attempts succeeded.");
      else if (t.Status == TaskStatus.Faulted)
         Console.WriteLine("{0} ping attempts failed", failed);      
   }
}
// The example displays output like the following:
//       5 ping attempts failed

WhenAny使用方法类似,具体可以查看官方链接


4.任务延迟

任务延迟依靠的是Task。Delay方法,该方法通过创建一个Task对象,该对象暂停在线程中的处理,并在一定时间内完成。与Thread.Sleep不同在于:

Task.DelayThread.Sleep
异步延迟同步延迟
不阻塞线程阻塞线程
可以取消不能取消
创建一个规定时间的内的任务线程休眠一定时间

Delay的简单用法:

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var t = Task.Run(async delegate
              {
                 await Task.Delay(1000);
                 return 42;
              });
      t.Wait();
      Console.WriteLine("Task t Status: {0}, Result: {1}",
                        t.Status, t.Result);
   }
}
// The example displays the following output:
//        Task t Status: RanToCompletion, Result: 42

想要看出延迟效果你可以采用与运行计时器:

Stopwatch sw = Stopwatch.StartNew();
var delay = Task.Delay(1000).ContinueWith(_ =>
                           { sw.Stop();
                             return sw.ElapsedMilliseconds; } );

Console.WriteLine("Elapsed milliseconds: {0}", delay.Result);
// The example displays output like the following:
//        Elapsed milliseconds: 1013

来判断延迟是否起到了作用。要中途取消延迟,则需要CancellationToken参数:

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      CancellationTokenSource source = new CancellationTokenSource();

      var t = Task.Run(async delegate
              {
                 await Task.Delay(TimeSpan.FromSeconds(1.5), source.Token);
                 return 42;
              });
      source.Cancel();
      try {
         t.Wait();
      }
      catch (AggregateException ae) {
         foreach (var e in ae.InnerExceptions)
            Console.WriteLine("{0}: {1}", e.GetType().Name, e.Message);
      }
      Console.Write("Task t Status: {0}", t.Status);
      if (t.Status == TaskStatus.RanToCompletion)
         Console.Write(", Result: {0}", t.Result);
      source.Dispose();
   }
}
// The example displays output like the following:
//       TaskCanceledException: A task was canceled.
//       Task t Status: Canceled

delay也有好几个版本:

 


5.异步Lambda表达式

可以使用异步的匿名方法和异步Lambda表达式,这种构造很适合只有少量工作的处理程序。下面是一个Lambda表达式注册为一个按钮点击事件的处理程序:

startWorkButton.Click+=Async(sender,e)=>
{
    // 处理其它任务
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值