咱今天来聊聊一个毁灭世界的故事,哦,不,是一个使用 TaskCompletionSource 让偷核武器,哦,又说错了,是让事件转换为异步的方法,让咱可以在一个方法里面顺序写下毁灭世界的逻辑
故事的背景是这个世界上的核导弹的发射是只要有密码就能发射,而刚好咱有一个强大的黑客团队可以窃取到密码。咱想要写一个方法,这个方法可以按照顺序发布一些指令,包括让黑客团队窃取密码,然后发射导弹,等待世界毁灭
因为黑客团队都很神秘,请动黑客团队去窃取密码之后,不会从原先的方法返回。而是黑客团队为了安全性(世界都要毁灭了,哪来的安全性) 会通过比特信告诉你,拿到密码了。等等,什么是比特信?这是一个超级高加密的匿名的邮箱 P2P 服务,反正对咱程序来说就是一个事件,定义如下
代码语言:javascript复制 static class BtcMessage
{
public static event EventHandler KeyReceived;
}
static class HackTeam
{
public static void PeekKey()
{
}
}
如上面的定义,黑客团队 HackTeam 有一个公开的方法是窃取密码,具体做啥,神秘的黑客可不会告诉咱。只是知道在完成之后,咱 BtcMessage 的 KeyReceived 事件将会触发。咱可以进行下一步的行动
下一步就是发射导弹了,尽管发射的过程很复杂,但对咱来说也是一个函数调用的事情
代码语言:javascript复制 static class Missiles
{
public static void Fire()
{
}
}
发射完成导弹之后,是不是这个世界就毁灭了?还不是,还需要等待导弹落地,爆炸,然后等待生态环境凉凉…… 没关系,最后这个世界会告诉咱世界已经毁灭了,因为这个世界是有意识的
代码语言:javascript复制 static class World
{
public static event EventHandler Broke;
}
好,那么咱的逻辑可以如何组织。第一步让黑客团队获取密码,等待 BtcMessage 事件回调。在 BtcMessage 事件触发之后调用导弹发射。然后等待世界触发毁灭事件
按照最简单的逻辑应该是这样写的,本来是想做个 WPF 程序,点击按钮就执行毁灭世界的。不过看看还是控制台简单
代码语言:javascript复制 static void Main(string[] args)
{
World.Broke = (sender, eventArgs) =>
{
Console.WriteLine("Hello World!");
};
BtcMessage.KeyReceived = (sender, eventArgs) =>
{
Missiles.Fire();
};
HackTeam.PeekKey();
}
可以看到代码不清真,因为都是倒过来写的,不倒过来也不成,因为如果在动作执行之后再监听事件,说不定事件就执行完成了
这就需要标题的 TaskCompletionSource 出场了,这个类的作用就是支持等待,同时可以被设置完成,很不好理解。写写代码就知道了
添加了 TaskCompletionSource 的写法如下
代码语言:javascript复制 HackTeam.PeekKey();
await btcReceivedTask.Task;
Missiles.Fire();
await worldBrokeTask.Task;
Console.WriteLine("Hello World!");
是的按照顺序了写下来了,但是 btcReceivedTask 和 worldBrokeTask 是什么?这是先准备好的 TaskCompletionSource 对象
代码语言:javascript复制 var btcReceivedTask = new TaskCompletionSource<bool>();
var worldBrokeTask = new TaskCompletionSource<bool>();
BtcMessage.KeyReceived = (sender, eventArgs) =>
{
btcReceivedTask.SetResult(true);
};
World.Broke = (sender, eventArgs) =>
{
worldBrokeTask.SetResult(true);
};
为什么需要使用 TaskCompletionSource<bool>
是因为 TaskCompletionSource 只有泛形的版本,而 SetResult
方法一调用,将会让等待的代码继续往下执行
也就是在代码执行到 await btcReceivedTask.Task;
的时候,将会进入等待。在 btcReceivedTask.SetResult
方法被调用之后,才会继续执行 await btcReceivedTask.Task;
之后的代码
于是在 TaskCompletionSource 的辅助之后的代码,写毁灭世界的逻辑请看来就清真了
当然,一开始的代码还可以封装一下,咱可以封装出等待任意事件的触发作为异步的代码
例如封装一个世界被毁灭的等待任务
代码语言:javascript复制 public class WorldBrokeTask
{
public WorldBrokeTask()
{
World.Broke = World_Broke;
}
private void World_Broke(object sender, EventArgs e)
{
World.Broke -= World_Broke;
TaskCompletionSource.SetResult(true);
}
public Task WaitWorldBroke()
{
return TaskCompletionSource.Task;
}
private TaskCompletionSource<bool> TaskCompletionSource { get; } = new TaskCompletionSource<bool>();
}
这个封装可以协助咱准备监听是哪一次的世界毁灭,这样咱就不需要处理具体的事件监听逻辑。如一开始的代码其实存在一个坑就是当在毁灭世界之后,下一次世界毁灭的时候,又会触发事件。如果不是创建方法,那么很难做到只监听一次
通过封装之后的使用如下
代码语言:javascript复制 var worldBrokeTask = new WorldBrokeTask();
HackTeam.PeekKey();
await btcReceivedTask.Task;
Missiles.Fire();
await worldBrokeTask.WaitWorldBroke();
Console.WriteLine("Hello World!");
可以看到创建出来 WorldBrokeTask 然后接着等待就可以了,代码很简单
通过本文的例子相信大家也掌握了毁灭世界,哦,不,使用 TaskCompletionSource 封装事件为异步的方法
当然本文也回答了一个问题,是否使用 await 就存在线程的切换。其实可以不用切换线程