.NET标准库在.NET Core中失败,但在框架中没有失败。

7
我们需要使用第三方供应商(perforce)的API。直到今天,我们能够在.net框架应用程序中引用和使用该API。但是,现在我们正在重构我们的产品,并且当然决定使用新的.net环境,即.net core 2.2。由于Perforce没有为.net core发布该库,因此我们决定将该库移植到.net standard。
简而言之,我们下载了源代码,进行了移植,并将其添加为.net core项目的引用。
到目前为止,一切都很好。奇怪的是,在使用该库后,我们从该库得到ExecutionEngineException,它会触发Environment.Failfast并终止应用程序。
还有一个重要信息,该库使用另一个本地库(p4bridge.dll)。
异常如下:
FailFast:
A callback was made on a garbage collected delegate of type 'p4netapi!Perforce.P4.P4CallBacks+LogMessageDelegate::Invoke'.

   at Perforce.P4.P4Bridge.RunCommandW(IntPtr, System.String, UInt32, Boolean, IntPtr[], Int32)
   at Perforce.P4.P4Bridge.RunCommandW(IntPtr, System.String, UInt32, Boolean, IntPtr[], Int32)
   at Perforce.P4.P4Server.RunCommand(System.String, UInt32, Boolean, System.String[], Int32)
   at Perforce.P4.P4Command.RunInt(Perforce.P4.StringList)
   at Perforce.P4.P4CommandResult..ctor(Perforce.P4.P4Command, Perforce.P4.StringList)
   at Perforce.P4.P4Command.Run(Perforce.P4.StringList)
   at Perforce.P4.Client.runFileListCmd(System.String, Perforce.P4.Options, System.String, Perforce.P4.FileSpec[])
   at Perforce.P4.Client.SyncFiles(System.Collections.Generic.IList`1<Perforce.P4.FileSpec>, Perforce.P4.Options)

我已经了解与“垃圾回收委托”相关的消息。似乎在某些地方将委托指针传递给非托管库,然后GC将其回收。
我们查看了该API的源代码。我们看到了一些可能导致该错误的原因。但是这只是一个想法。
在调查故障时,我们创建了另一个引用了该移植库的.net框架应用程序,然后我们没有遇到任何.net框架中的错误。
我的问题是:
1. .net框架和.net core在垃圾收集机制方面有什么区别吗? 2. 怎么可能,.net框架和.net core以不同的方式响应相同的库?

1
“在垃圾回收机制方面,.NET Framework和.NET Core有什么区别吗?” - 当然有!GC一直在不断发展;但是,我猜测如果以前它能够工作,最有可能的情况是它以“未定义行为”的方式意外地工作;您能否提供有关如何与其他库共享此回调的更多详细信息? - Marc Gravell
".net框架和.net core是如何以不同的方式响应相同库的?" 这是因为JIT、GC和BCL都经历了大量的改变。很多代码不会受到不利的影响,但有些情况确实会有影响!即使像询问“什么是‘默认’文本编码?”这样简单的问题(其中“默认”一词完全是错误的)也有根本性的不同…… - Marc Gravell
1
@MarcGravell 感谢您提供的信息。实际上,我们发现一个地方他们正在传递指针到非托管库,并且在某些时候他们正在重新初始化该变量内部的 .net api。实际上,在调查源代码后,我们发现它在 .net framework 中的工作方式很奇怪。 - Farhad Jabiyev
1
实际上,如果您将指针传递给非托管代码,那么很容易生成简单地出现问题的代码;所有“简单”的选项仅在单个P/Invoke调用或fixed块的持续时间内定义良好;对于大多数长期场景,您需要通过GCHandle处理手动固定;这种类型的P/Invoke代码很容易出错。因此:如果它是错误的,我们又处于未定义的领域,在那里错误的代码可以“工作”,但只能在小范围内“工作”;我不会惊讶于这种代码在框架切换时变得脆弱。 - Marc Gravell
1个回答

5
我想发表对我的问题的答案,因为我已经解决了这个问题。而且,由于你现在正在阅读我的回答,这意味着你遇到了类似的问题。首先,我要感谢 @MarcGravell 的评论,并建议你阅读评论。
首先,我想总结一下对第二个问题的回答:
“.net框架和.net core怎么可能以不同的方式处理相同的库?”
老实说,一开始我认为 .net标准库将在所有地方都表现得一致。但是,我错了。
让我们从".Net Standard"的定义开始。".NET Standard"只是一种规范(将其视为接口),它仅声明特定版本的平台公开哪些类型和API。
如果您看一下,您会发现标准库引用了包含我们可以从库项目内部使用的API或者更好地说是合同的"netstandard.dll"的.NET Standard SDK。如果您使用 "Ildasm.exe" 查看标准库,您会发现它正在使用 ".net standard.dll"。 enter image description here 让我们来看看它在我们的core和framework应用程序中是如何使用的。在核心中,它比框架工作得更好。
我总是喜欢图表: enter image description here 正如您在图表中所见,核心和框架应用程序都引用了它们自己的"netstandard.dll"。这些库基于"type forwarding"的概念构建。对于核心应用程序,BCL类型由"System.Runtime.dll"提供,而在框架应用程序的情况下,BCL类型由"mscorlib.dll"提供。因此,有两个不同的实现
例如,这里是".net standard"库的IL代码,它将由核心应用程序使用: enter image description here

阅读更多关于来自 MSDN 的类型转发的信息。

有关 .NET Standard 的详细信息来源


现在对我的第一个问题进行一些讨论:

就垃圾收集机制而言,.Net Framework 和 .Net Core 之间有什么区别吗?

事实上,微软不断地发展GC机制,这一点不应该让人感到惊奇。


现在是我如何解决这个问题的过程:

好在我将移植到标准库中的库是开源的。我调查了这个问题,简单来说,它们将委托指针传递给非托管库。那个非托管库保存了指针并尝试在某个生命周期内使用它,这个生命周期比指针的生命周期长。实际上,有趣的一点是框架 GC 并没有以某种方式收集这个委托,这就是为什么框架没有异常的原因。但是,在 Core 中 GC 收集了这个委托并创建了失败的原因。我改变了设置该字段的方式,现在它可以工作了。我将该字段声明为静态的,并在静态构造函数中初始化它,因此该委托的生命周期与应用程序的生命周期一样长。


1
我真的很高兴你找到了答案! - Marc Gravell
1
由于垃圾回收机制不是确定性的,因此像这样的错误很常见,可能会长时间未被发现。 - David Browne - Microsoft
2
非常感谢您发布这篇文章以及提供的答案。显然,您在此方面花费了很多时间。有一个请求...作为P4API.NET的当前维护者,我非常想知道您为解决此问题所做的确切更改,以便我可以将它们纳入下一个版本中。您能否提供源代码更改?非常感谢! - Norman Morse

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接