如何避免Task内联,你确定异步任务已停止执行?
【导读】目前私下空余时间在研究远程大文件断点续传下载,正在进行时,正当研究Task时,看到有个Unwrap方法基本没怎么用过,这里记录并学习下
说到Task.Run和Task.Factory.StartNew二者区别以及应该推崇使用哪一个,我也建议使用Task.Run,这里不做过多介绍,网上资料一大把。
Task代理Unwrap避免内联
那么使用该方法可以解决什么问题呢?首先我们来看如下一个简单例子
Task<Task<int>> call = Task.Factory.StartNew(async () =>
{
return await DoSomethingAsync();
});
我们通过Task.Factory.StartNew异步方法,此时获取内联任务结果将通过嵌套Task包裹,那么如果要是多层嵌套呢?那也没问题,我们可以直接将返回结果通过var声明即可,就不用再写层层嵌套Task。
这只是其一,事实情况在于对于多层内联Task,我们如何处理以及取消令牌等过程中发生的错误。Unwrap方法的作用就在于此,如下:
Task<int> call = Task.Factory.StartNew(async () =>
{
return await DoSomethingAsync();
}).Unwrap();
直接从该方法单词入手,暂且翻译为“摊开或展开”,就像洋葱般一层层剥开,让我们看到最终想要的东西,我们也看看源码怎么讲
public static Task<TResult> Unwrap<TResult>(this Task<Task<TResult>> task)
{
if (task == null)
{
throw new ArgumentNullException(nameof(task));
}
return
!task.IsCompletedSuccessfully ? Task.CreateUnwrapPromise<TResult>(task,
lookForOce: false) :
task.Result ??
Task.FromCanceled<TResult>(new CancellationToken(true));
}
利用Unwrap方法创建一个代理,它将为我们处理所有复杂逻辑,同时我们也可以不用转发取消令牌,它可以为我们排查所有不同级别的错误,换言之,它所返回的Task包含所有令牌取消请求和异常处理(依托代理与内联Task协调工作)就像我们执行一个简单的任务一样,而不用像任务中的任务而进行嵌套
var call1 = Task.Run(async () =>
{
return await DoSomethingAsync();
});
Task.Run和Task.Factory.StartNew区别也在于此,因为利用Task.Run直接将返回最终的实际结果,所以对于内联Task,利用Task.Run来的更加实际,而利用Task.Factory.StartNew若没调用Unwrap可能会出现结果不符合我们所预期
同样,利用Task.Factory.StartNew进行追加任务(ContinueWith)时,也会返回一个任务内联另外一个任务,通过使用Unwrap方法即可避免这种情况。
上述只是我个人对使用Unwrap方法的概括和总结,这里给出源码中注释解释翻译:
到了这里,想必我们已经清楚Unwrap方法的作用,我们来做个练习:若启动一个长时间执行的异步任务,那么如何停止这个异步任务?
public class UnwrapPractice
{
CancellationTokenSource cancellationToken;
public void Start()
{
cancellationToken = new CancellationTokenSource();
Task.Factory.StartNew(
function: ExecuteAsync,
cancellationToken: cancellationToken.Token,
creationOptions: TaskCreationOptions.LongRunning,
scheduler: TaskScheduler.Default);
}
public void Stop()
{
cancellationToken.Cancel();
}
async Task ExecuteAsync()
{
Console.WriteLine("进入执行操作");
while (!cancellationToken.IsCancellationRequested)
{
//模拟长时间执行操作
await Task.Delay(30000);
}
Console.WriteLine("退出执行操作");
}
}
通过上述简单代码实现作业练习,接下来我们在控制台调用启动和停止,看看结果是否符合预期
static async Task Main(string[] args)
{
var unwrapPractice = new UnwrapPractice();
Console.WriteLine("启动");
unwrapPractice.Start();
Thread.Sleep(3000);
Console.WriteLine("停止");
unwrapPractice.Stop();
Console.Read();
}
反观上述动态图,此时我们将发现长时间异步任务通过调用取消令牌根本没有停止(在控制台并没有打印出【退出执行操作】字眼),此时我们可利用Unwrap方法创建此任务代理,然后在调用令牌取消方法后,再执行代理的等待方法,等待取消任务直至完成即可,貌似有点Thread中Join方法的意味,如下:
public class UnwrapPractice
{
Task task;
CancellationTokenSource cancellationToken;
public void Start()
{
cancellationToken = new CancellationTokenSource();
Task<Task> _task = Task.Factory.StartNew(
function: ExecuteAsync,
cancellationToken: cancellationToken.Token,
creationOptions: TaskCreationOptions.LongRunning,
scheduler: TaskScheduler.Default);
//获取此任务代理
task = _task.Unwrap();
}
public void Stop()
{
cancellationToken.Cancel();
//等待取消任务完成(重要)
task.Wait();
}
async Task ExecuteAsync()
{
Console.WriteLine("进入执行操作");
while (!cancellationToken.IsCancellationRequested)
{
//模拟长时间执行操作
await Task.Delay(50000);
}
Console.WriteLine("退出执行操作");
}
}
💡 由此可见,对于长时间异步任务的执行,当我们想让其停止时,我们想当然的认为只需调用取消令牌的Cancel方法就可万事大吉,经练习实践证明其实依然在执行,并没有完全停止!
💡 若利用Task.Run直接调用Wait方法即可,我们也发现,当我们需要做更多处理,使用Task.Factory.StartNew时,若对各种选项配置没有一个很清楚的认识和理解,很容易用出毛病!
本文分享自微信公众号 - JeffckyShare(JeffckyShare)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
openEuler Summit 2020 来了!
openEuler Summit 是由 openEuler 社区举办的开发者交流会,首届线下 openEuler Summit 将于 12 月在北京举行。 openEuler Summit 广泛邀请操作系统生态的开发者、用户、社区贡献者、软件爱好者共同解读 openEuler 的最新版,探讨未来的技术路线,让技术、生态、商业在这里产生奇妙的化学反应。 开源是一种态度、分享是一种精神。Call for Speaker、Call for Sponsor、Call for SIG、Call for Demo 现已全面开放报名。 我们诚挚的邀请您报名演讲、演示 Demo、发表案例、参与社区建设。 期待您的到来。 openEuler —— 最具活力的开源社区 本文分享自微信公众号 - openEuler(openEulercommunity)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
- 下一篇
why哥悄悄的给你说几个HashCode的破事。
这是why技术的第 72 篇原创文章 Hash冲突是怎么回事 在这个文章正式开始之前,先几句话把这个问题说清楚了:我们常说的 Hash 冲突到底是怎么回事? 直接上个图片: 你说你看到这个图片的时候想到了什么东西? 有没有想到 HashMap 的数组加链表的结构? 对咯,我这里就是以 HashMap 为切入点,给大家讲一下 Hash 冲突。 接着我们看下面这张图: 假设现在我们有个值为 [why技术] 的 key,经过 Hash 算法后,计算出值为 1,那么含义就是这个值应该放到数组下标为 1 的地方。 但是如图所示,下标为 1 的地方已经挂了一个 eat 的值了。这个坑位已经被人占着了。 那么此时此刻,我们就把这种现象叫为 Hash 冲突。 HashMap 是怎么解决 Hash 冲突的呢? 链地址法,也叫做拉链法。 数组中出现 Hash 冲突了,这个时候链表的数据结构就派上用场了。 链表怎么用的呢?看图: 这样问题就被我们解决了。 其实 hash 冲突也就是这么一回事:不同的对象经过同一个 Hash 算法后得到了一样的 HashCode。 那么写到这里的时候我突然想到了一个面试题: ...
相关文章
文章评论
共有0条评论来说两句吧...