在第一节中我们介绍了基于任务的异步编程,并给出了几种创建和运行异步方法的例子,在本节中,我们将讲解如何管理我们创建的任务,这些管理主要是取消、等待,延迟。
目录
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.Delay | Thread.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)=>
{
// 处理其它任务
};