首页 新闻 会员 周边

.net core 多线程,数据保存的时候Context被释放

0
悬赏园豆:100 [已解决问题] 解决于 2018-07-23 15:16

.net core 多线程TCP,接收到数据的时候存入到数据库中。但是尝试过很多方法,总是保存的时候Context disposed;下面是代码:

TCP 代码:

接收数据后处理的代码:

这里是AyomarContentService的代码:

这是仓储的代码:

 

这是错误:

DI:

 

网上搜遍了资料,一直没找到解决方案,忘大神指点!

果冻布丁喜之郎的主页 果冻布丁喜之郎 | 初学一级 | 园豆:114
提问于:2018-07-21 18:36
< >
分享
最佳答案
0

是控制台程序不是 ASP.NET Core 程序?

收获园豆:100
dudu | 高人七级 |园豆:31007 | 2018-07-21 21:36

如果不是 ASP.NET Core 程序,你要自己管理 Scoped 的生命周期

dudu | 园豆:31007 (高人七级) | 2018-07-21 21:40

@dudu: 不是控制台程序,.net core web程序,我之前看到过你也遇到过生命周期的问题,但是我们的情况不太一样,我这个就是因为多线程的问题,有网关通过TCP连接到服务器,上报数据,程序多线程监听,每个线程监听一个连接,问题就是子线程监听到数据后,调用注入的service处理数据,这时候context已经被释放了,尝试了很多注入方法,都失败了。我尝试做了一个webapi,在子线程监听到数据之后通过HttpWebRequest请求这个api来处理数据是没有问题的,但是这不是解决这个问题最有效的方式

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 09:01

@果冻布丁喜之郎: 在 TCPService 的 receive 方法中不要使用成员变量 _ayomarContentService ,用 IServiceProvider.GetRequiredService 重新解析一个 AyomarContentService 实例

dudu | 园豆:31007 (高人七级) | 2018-07-22 09:48

@dudu: 我试过,在主线程构造函数注入IServiceProvider,到子线程GetRequiredService,IServiceProvider会被disposed。我又尝试IServiceCollection重新IServiceProvider serviceProvider来BuildServiceProvider(),但是所有的AyomarContentService里面的包括仓储的实例,DBContext,以及它继承的BaseService里面的所有实例,都要重新在IServiceCollection里注入一次。

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 10:34

@果冻布丁喜之郎: 那就是 TCPService 实例的生命周期问题了,难道你把 TCPService 也注册为 Scoped 了?

dudu | 园豆:31007 (高人七级) | 2018-07-22 10:47

@dudu: 也不能这么说,TCPService 实例里的都可以的,就是这个实例里的多线程。主线程是没问题的。TcpService是单例

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 10:52

@果冻布丁喜之郎: TCPService 是单例的话,它的构造函数注入了 AyomarContentService ,AyomarContentService 也相当于是单例了

dudu | 园豆:31007 (高人七级) | 2018-07-22 11:01

@果冻布丁喜之郎: 推荐阅读一篇英文博文:ASP.NET Core Dependency Injection Best Practices, Tips & Tricks

dudu | 园豆:31007 (高人七级) | 2018-07-22 11:02

@果冻布丁喜之郎: 我觉得问题就出在单例的 TCPService 注入了不是单例的 AyomarContentService

dudu | 园豆:31007 (高人七级) | 2018-07-22 11:05

@dudu: AyomarContentService 也是单例

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 11:07

@dudu: 这是TCP和AyomarContentService

这是仓储部分

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 11:09

@果冻布丁喜之郎: 这不是单例,单例是 AddSingleton

dudu | 园豆:31007 (高人七级) | 2018-07-22 11:11

@果冻布丁喜之郎: 可以试试全部 AddTransient ,不使用 AddScoped

dudu | 园豆:31007 (高人七级) | 2018-07-22 11:16

@dudu: 说错了,不是单例,没有用到过单例,所有的回话都用同一个对象,感觉很危险。基本 DBContext和仓储都是Scoped,这是服务都是Transient

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 11:17

@dudu: Context也用Transient吗?我试试

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 11:18

@dudu: 还是被释放了,全换成了Transient

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 11:22

@dudu: 之前也全部是Transient,在linux会引发警告More than twenty 'IServiceProvider' instances have been created for internal use by Entity Framework. This is commonly caused by injection of a new singleton service instance into every DbContext instance. For example, calling UseLoggerFactory passing in a new instance each time--see https://go.microsoft.com/fwlink/?linkid=869049 for more details. Consider reviewing calls on 'DbContextOptionsBuilder' that may require new service providers to be built. 所以改成了Scoped

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 11:31

@果冻布丁喜之郎: 建议提供一下 AppDbContext 的代码以及 Startup 中 services.AddDbContext 的代码

dudu | 园豆:31007 (高人七级) | 2018-07-22 11:43

@果冻布丁喜之郎: services.AddDbContext 也可以指定 ServiceLifetime

dudu | 园豆:31007 (高人七级) | 2018-07-22 11:45

@dudu: 刚才全部换成Transient的时候指定了。

这是Setup中的:

这是AppDbContext:

这是仓储中的AppDbContext:

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 11:48

@果冻布丁喜之郎: 能否提供重现这个问题的示例代码放到 github 上,我下午找时间看一下

dudu | 园豆:31007 (高人七级) | 2018-07-22 11:52

@dudu: 我放网盘里给你吧,包括数据库。

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 11:54

@dudu: 网盘链接私信给你了

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 11:59
dudu | 园豆:31007 (高人七级) | 2018-07-22 17:53

@果冻布丁喜之郎: 将 AppDbContext 注册为单例可以避开这个问题

services.AddDbContext<AppDbContext>(..., ServiceLifetime.Singleton);

暂时还没找到更好的解决方法

dudu | 园豆:31007 (高人七级) | 2018-07-22 18:20

@果冻布丁喜之郎: 这个问题实际就是在新建的线程中无法访问被依赖注入容器管理的实现 IDispose 接口的类型的实例(单例除外)

dudu | 园豆:31007 (高人七级) | 2018-07-22 18:24

@dudu: 单例我怕同一个Context对象会引起问题。我尝试下mq吧

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-23 08:54

@果冻布丁喜之郎: 不用单例也能实现,只是比较麻烦,等会我提供几个思路

dudu | 园豆:31007 (高人七级) | 2018-07-23 09:07

@果冻布丁喜之郎:
基于现有的代码,推荐的解决方法是:从 Ayomar.Core.ServicesImp.AyomarContentService 下手,通过构造函数注入 DbContextOptions (它是单例),用 DbContextOptions 手工 new AppDbContext 重写 SaveAsync 与 UpdateAsync ,实测有效

public class AyomarContentService : Repository<AyomarContents>, IService.IAyomarContentService
{
    private readonly DbContextOptions _options;

    public AyomarContentService(AppDbContext Context, DbContextOptions options) : base(Context)
    {
        _options = options;
    }

    public override async Task<bool> SaveAsync(AyomarContents entity, bool IsCommit = true)
    {
        using (var context = new AppDbContext(_options))
        {
            context.Set<AyomarContents>().Add(entity);
            if (IsCommit)
                return await context.SaveChangesAsync() > 0;
            else
                return false;
        }
    }

    public override async Task<bool> UpdateAsync(AyomarContents entity, bool IsCommit = true)
    {
        using (var context = new AppDbContext(_options))
        {
            context.Set<AyomarContents>().Attach(entity);
            context.Entry<AyomarContents>(entity).State = EntityState.Modified;
            if (IsCommit)
                return await context.SaveChangesAsync() > 0;
            else
                return false;
        }
    }
}
dudu | 园豆:31007 (高人七级) | 2018-07-23 11:40

@dudu: 谢谢,我试一下。我昨天尝试用HttpClientFactory访问api,也可以,但是由于TCP节点太多,导致mysql超过连接数了,估计还必须走MQ,我先测下你这个方案。

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-23 12:45

@果冻布丁喜之郎: 有人在我的博客中留言提供了最简单的解决方法 —— 用 Task.Run() 取代 Thread.Start() ,实测有效:
1)用 await Task.Run(() => watchconnecting()); 取代 threadwatch = new Thread(watchconnecting); 部分的代码
1)用 Task.Run(() => receive(connection)); 取代 Thread thread = new Thread(pts); 部分的代码

dudu | 园豆:31007 (高人七级) | 2018-07-23 21:53

@dudu: 这是用异步取代多线程,我之前也有考虑,但是没有尝试,都是防止阻塞,应该也是个很好的方案,我一会弄完mq 试一下.

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-24 10:01

@果冻布丁喜之郎: 这不是“异步取代多线程”,Task.Run 会使用新的线程执行任务

dudu | 园豆:31007 (高人七级) | 2018-07-24 10:14

@dudu: 实测不行,只能有一个tcp 成功

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-24 10:29

@果冻布丁喜之郎: 这应该是 Task.Run 使用上的问题

dudu | 园豆:31007 (高人七级) | 2018-07-24 10:37

@dudu: 用法应该是这样吧

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-24 10:42

@果冻布丁喜之郎: 我测试时,没有把 watchconnecting 改为 async

dudu | 园豆:31007 (高人七级) | 2018-07-24 11:01

@dudu: 用Task.run执行同步方法确实可行,都能发送数据,不过好像在接收的时候有的时候会阻塞一下,导致接收两次甚至更多的数据,如

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-24 11:16

@dudu: 这是执行了_ayomarConentServices.SaveAsync().Result;换成 Task.Run(async ()=> await _ayomarConentServices.SaveAsync()).就不会出现这样的问题了。只是很容易,mysql的连接池就满了,这个我自己看下是不是没有及时释放。 非常感谢!

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-24 11:28

@果冻布丁喜之郎: tcp server 的实现推荐2个参考资料:

dudu | 园豆:31007 (高人七级) | 2018-07-24 11:30

@果冻布丁喜之郎: 千万不要在同步方法中以 .Result 调用异步方法

dudu | 园豆:31007 (高人七级) | 2018-07-24 11:32

@dudu: 看了这两篇文章,基本和我们改成Task之后是一样的,只不过在启动的时候,他是直接执行了listen(),而我们是执行了 Task.Run(()=>listen()),这样可以在点击“启动TCP服务”按钮之后不会阻塞,给用户提示“TCP启动成功”以及返回 当前连接的客户端列表,更友好一些

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-24 14:23

@dudu: 你测试的时候有数据到数据库吗?数据库没有数据,我刚断点看了下,用Task.Run()一样 Context被释放

果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-24 17:04

@果冻布丁喜之郎: 的确有问题,正在看

dudu | 园豆:31007 (高人七级) | 2018-07-24 17:40

@果冻布丁喜之郎: 的确用 Task.Run 也是同样的问题,还是要采用之前的解决方法

dudu | 园豆:31007 (高人七级) | 2018-07-24 17:47
其他回答(2)
0

吧上下文字段改成线程静态.

然后写一个get属性.给上下文做懒加载.

吴瑞祥 | 园豆:29449 (高人七级) | 2018-07-21 21:38

整个程序的设计是,ef的仓储,然后有个算是DDD的service 来处理,这个service 注入仓储的实例,不管是mvc的控制器还是其它地方,只要处理这个数据都通过这个service,问题在于,子线程调用这个service的时候,context被释放。

支持(0) 反对(0) 果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 09:04

@果冻布丁喜之郎: 那你找到他是在什么地方什么时候被释放的吗?

支持(0) 反对(0) 吴瑞祥 | 园豆:29449 (高人七级) | 2018-07-22 10:43

@吴瑞祥: 第三张图

支持(0) 反对(0) 果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 10:46

@果冻布丁喜之郎: 我说的是被释放.不是已经被释放的时候抛异常.

支持(0) 反对(0) 吴瑞祥 | 园豆:29449 (高人七级) | 2018-07-22 10:46

@吴瑞祥: 这个怎么看?

支持(0) 反对(0) 果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 11:02

@果冻布丁喜之郎: 看了上面的回复.

你这个东西是用了依赖注入.那生命周期管理你是怎么配置的?

支持(0) 反对(0) 吴瑞祥 | 园豆:29449 (高人七级) | 2018-07-22 11:03

@吴瑞祥: 这是TCP和AyomarContentService

仓储部分:

支持(0) 反对(0) 果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 11:10

@果冻布丁喜之郎: 那上下文的生命周期你是怎么管理的.

PS:上面这几个截图没意义.

支持(0) 反对(0) 吴瑞祥 | 园豆:29449 (高人七级) | 2018-07-22 11:19

@吴瑞祥: 上下文

支持(0) 反对(0) 果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 11:25

@果冻布丁喜之郎: 那你的多线程是在哪里体现的?

支持(0) 反对(0) 吴瑞祥 | 园豆:29449 (高人七级) | 2018-07-22 11:34

@吴瑞祥: 监听客户端消息

支持(0) 反对(0) 果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 11:36

@果冻布丁喜之郎: 那就在监听里每次触发时都从容器中取一次服务对象.

支持(0) 反对(0) 吴瑞祥 | 园豆:29449 (高人七级) | 2018-07-22 11:51

@吴瑞祥: 试过了,因为里面牵涉的服务太多,都要重新注册一次。暂时还是接收到数据后,通过创建http post一个api来解决。

支持(0) 反对(0) 果冻布丁喜之郎 | 园豆:114 (初学一级) | 2018-07-22 12:01
0

认真的看了好久,谷歌搜了这么个解决方案 ,楼主可以试试(链接):

又见阿郎 | 园豆:163 (初学一级) | 2018-08-22 21:07
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册