为什么要小心使用 Task.Run
昨天在博客园有园友问了我一个问题,是这样的:
先是半个月前 @碧水青荷 童鞋的一句话“大家都说不要随便 Task.Run(()=>{})
这样写”,当时没有想太多,这句话并没有引起我注意,只顾着回答他“不想在代码中加 async/await
该怎么做”的问题。
然后这句话被 @裤兜 童鞋注意到,昨天问了我为什么。我当时也很纳闷,Task.Run
在并行场景中很常见啊,为什么大家会有不要随便使用的说法。很遗憾,我当时脑海里认为这种说法只是空穴来风,并没有细究。
我有个习惯,就是下班路上在地铁上快速复盘一下今天发生的事情。当时这个问题刚好就在脑海里闪现了一下,“为什么大家都说不要随便使用 Task.Run
”。突然想起了多年前的一个晚上……哦,难道是“Ta”?
对,应该就是它,内存泄露,除了这个原因我再也想不到其它原因了。因为我隐约记得多年前我确实踩过一次这个坑,也可能是两次。
没错,Task.Run
使用不当,一不留意就会有内存泄露的问题。
我们先来看一段代码:
public class MyClass
{
private int _id;
private Logger<MyClass> _logger;
public MyClass(Logger<MyClass> logger)
{
_logger = logger;
}
public Task Foo(Logger<MyClass> logger)
{
return Task.Run(() =>
{
_logger.LogInformation($"Executing job with ID {_id}");
// do sth.
});
}
}
在这段代码中,私有成员 _id
被 Task.Run
的匿名方法捕获使用,进而导致 MyClass
实例被引用。当外部使用完 MyClass
实例时,本该由 GC 回收的时候却发现它还被其它资源引用着,所以 GC 认为该实例不应用被回收,也就永远失去了被回收的机会。
道理很简单,我就不再用示例演示了。解决办法也很简单,想必很多人都知道,就是使用本地变量。
public class MyClass
{
private int _id;
private Logger<MyClass> _logger;
public MyClass(Logger<MyClass> logger)
{
_logger = logger;
}
public Task Foo(Logger<MyClass> logger)
{
var localId = _id;
return Task.Run(() =>
{
_logger.LogInformation($"Executing job with ID {localId}");
// do sth.
});
}
}
通过将值分配给一个本地变量,类就没有成员被捕获,即避免了潜在的内存泄漏。
内存泄漏问题在 Task.Run
身上发生很常见,容易被大家记住,容易提高警觉。其实不光是 Task.Run
,其它地方使用了匿名方法也同样要小心,比如这个示例:
public class MyClass
{
private int _id;
private Logger<MyClass> _logger;
private JobQueue _jobQueue;
public MyClass(Logger<MyClass> logger, JobQueue jobQueue)
{
_logger = logger;
_jobQueue = jobQueue;
}
public void Foo()
{
_jobQueue.EnqueueJob(() =>
{
_logger.LogInformation($"Executing job with ID {_id}");
// do sth.
});
}
}
也有内存泄漏的问题。
总之,任何使用匿名方法的地方都要避免捕获类的成员,小心内存泄漏。
为什么要小心使用 Task.Run的更多相关文章
- 小心使用 Task.Run 续篇
关于前两天发布的文章:为什么要小心使用 Task.Run,对文中演示的示例到底会不会导致内存泄露,给很多人带来了疑惑.这点我必须向大家道歉,是我对导致内存泄漏的原因没描述和解释清楚,也没用实际的示例证 ...
- 小心使用 Task.Run 解惑篇
继上一篇文章之后,这篇文章主要解答以下两个疑惑: 由于值类型是拷贝的方式赋值,所以捕获的本地变量和类成员是指向的是各自的值,对本地变量的捕获不会影响到整个类.但如果把 _id 改为引用类型(如 Str ...
- 对 精致码农大佬 说的 Task.Run 会存在 内存泄漏 的思考
一:背景 1. 讲故事 这段时间项目延期,加班比较厉害,博客就稍微停了停,不过还是得持续的技术输出呀! 园子里最近挺热闹的,精致码农大佬分享了三篇文章: 为什么要小心使用 Task.Run [http ...
- (转).NET 4.5中使用Task.Run和Parallel.For()实现的C# Winform多线程任务及跨线程更新UI控件综合实例
http://2sharings.com/2014/net-4-5-task-run-parallel-for-winform-cross-multiple-threads-update-ui-dem ...
- Task.Run Vs Task.Factory.StartNew
在.Net 4中,Task.Factory.StartNew是启动一个新Task的首选方法.它有很多重载方法,使它在具体使用当中可以非常灵活,通过设置可选参数,可以传递任意状态,取消任务继续执行,甚至 ...
- Task.Run Vs Task.Factory.StartNew z
在.Net 4中,Task.Factory.StartNew是启动一个新Task的首选方法.它有很多重载方法,使它在具体使用当中可以非常灵活,通过设置可选参数,可以传递任意状态,取消任务继续执行,甚至 ...
- .Net4.0如何实现.NET4.5中的Task.Run及Task.Delay方法
前言 .NET4.0下是没有Task.Run及Task.Delay方法的,而.NET4.5已经实现,对于还在使用.NET4.0的同学来说,如何在.NET4.0下实现这两个方法呢? 在.NET4.0下, ...
- Task.Run与Task.Factory.StartNew的区别
Task是可能有延迟的工作单元,目的是生成一个结果值,或产生想要的效果.任务和线程的区别是:任务代表需要执行的作业,而线程代表做这个作业的工作者. 在.Net 4中,Task.Factory.Star ...
- C# Task.Run 和 Task.Factory.StartNew 区别
Task.Run 是在 dotnet framework 4.5 之后才可以使用,但是 Task.Factory.StartNew 可以使用比 Task.Run 更多的参数,可以做到更多的定制.可以认 ...
随机推荐
- Jetbrains全系列产品 2020最新激活方法 (即时更新)
即时更新:http://idea.itmatu.com/key Jetbrains全系列产品 2020最新激活方法 JMFL04QVQA-eyJsaWNlbnNlSWQiOiJKTUZMMDRRVlF ...
- JavaMail 发送邮件出现 Connection reset 问题
问题描述 使用 java mail 发送邮件的时候,申请的 163 邮箱作为发件箱,然无论如何配置,均出现 Connection reset,无法正常发送邮件. Exception in thread ...
- [开源] .Net 使用 ORM 访问 华为GaussDB数据库
前言 华为GaussDB是一个企业级AI-Native分布式数据库.GaussDB采用MPP(Massive Parallel Processing)架构,支持行存储与列存储,提供PB(Petabyt ...
- Redis基础—了解Redis是如何做数据持久化的
之前的文章介绍了Redis的简单数据结构的相关使用和底层原理,这篇文章我们就来聊一下Redis应该如何保证高可用. 数据持久化 我们知道虽然单机的Redis虽然性能十分的出色, 单机能够扛住10w的Q ...
- .net core集成JWT(基础)
关于JWT的基本概念,如果有不清晰的同学,请点击这里,就不在这里赘述了.接下来聊聊JWT是怎么发挥作用的. 第一,安装nuget包 Microsoft.AspNetCore.Authenticatio ...
- Elasticsearch(6):文档查询
为方便后续查询演示,我们先创建一个索引.创建索引请求如下:
- vue+echarts+datav大屏数据展示及实现中国地图省市县下钻
随着前端技术的飞速发展,大数据时代的来临,我们在开发项目时越来越多的客户会要求我们做一个数据展示的大屏,可以直观的展示用户想要的数据,同时炫酷的界面也会深受客户的喜欢. 大屏展示其实就是一堆的图表能够 ...
- innodb为什么需要doublewrite(转)
InnoDB的page size默认是16KB,而操作系统的一个block size是4KB,磁盘io block则更小.那么InnoDB的page刷到磁盘上要写4个操作系统block,在极端情况下( ...
- gcc 去除无用程序段
嵌入式系统中,对程序尺寸要求高时,可以使用本方法 代码中,经常会有一种情况,库中并非所有函数都会用到.然而,无用的函数也最终被编译进可执行文件中了. 为避免这一情况,可以在编译时,添加选项:-ffun ...
- docker生产——容器通信
简介 在接触docker的第一天起,大家应该就知道:docker容器使用沙箱机制,相互之间没有接口,一般情况下内部访问通过IP+端口.本地容器默认分配的IP极易发生变化,所以靠IP+端口访问的方式缺失 ...