我正在尝试找出引起的根本原因EngineExecutionException
。我将范围缩小到我认为是最小的可复制示例。
我有两个项目,一个是非托管C ++ DLL,另一个是托管C#控制台应用程序。非托管代码具有两个函数,一个存储一个回调,另一个调用它:
#define WINEXPORT extern "C" __declspec(dllexport)
typedef bool (* callback_t)(unsigned cmd, void* data);
static callback_t callback;
WINEXPORT void set_callback(callback_t cb)
{
callback = cb;
}
WINEXPORT void run(void)
{
callback(123, nullptr);
}
在C#方面:
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace ExecutionExceptionReproConsole
{
class Program
{
private const string dllPath = "ExecutionExceptionReproNative.dll";
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
private delegate bool callback_t(uint cmd, IntPtr data);
[DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
private static extern void set_callback(callback_t callback);
[DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
private static extern void run();
static async Task Main(string[] args)
{
set_callback(Callback);
while (!Console.KeyAvailable)
{
run();
await Task.Delay(1);
}
}
static bool Callback(uint cmd, IntPtr data)
{
return true;
}
}
}
当我运行控制台应用程序时,它可以正常运行三分半钟,然后System.EngineExecutionException
在run()
通话中崩溃。
调用堆栈:
[Managed to Native Transition] Annotated Frame
> ExecutionExceptionReproConsole.dll!ExecutionExceptionReproConsole.Program.Main(string[] args = {string[0x00000000]}) Line 26 C# Symbols loaded.
[Resuming Async Method] Annotated Frame
System.Private.CoreLib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) Unknown No symbols loaded.
System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Threading.Tasks.VoidTaskResult>.AsyncStateMachineBox<ExecutionExceptionReproConsole.Program.<Main>d__4>.MoveNext(System.Threading.Thread threadPoolThread) Unknown No symbols loaded.
System.Private.CoreLib.dll!System.Runtime.CompilerServices.TaskAwaiter.OutputWaitEtwEvents.AnonymousMethod__12_0(System.Action innerContinuation, System.Threading.Tasks.Task innerTask = Id = 0x000036d4, Status = RanToCompletion, Method = "{null}") Unknown No symbols loaded.
System.Private.CoreLib.dll!System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(System.Action action, bool allowInlining) Unknown No symbols loaded.
System.Private.CoreLib.dll!System.Threading.Tasks.Task.RunContinuations(object continuationObject) Unknown No symbols loaded.
System.Private.CoreLib.dll!System.Threading.Tasks.Task.TrySetResult() Unknown No symbols loaded.
System.Private.CoreLib.dll!System.Threading.Tasks.Task.DelayPromise.CompleteTimedOut() Unknown No symbols loaded.
System.Private.CoreLib.dll!System.Threading.TimerQueueTimer.CallCallback(bool isThreadPool) Unknown No symbols loaded.
System.Private.CoreLib.dll!System.Threading.TimerQueueTimer.Fire(bool isThreadPool) Unknown No symbols loaded.
System.Private.CoreLib.dll!System.Threading.TimerQueue.FireNextTimers() Unknown No symbols loaded.
是什么原因导致飞机坠毁?
其他一些信息:
Task.Delay
时间降低到0,或者如果我以同步循环而不是异步方式运行,则无法重现该问题。在这些情况下,我没有注意到内存使用量的增加。run()
在C ++代码中注释掉了回调调用,则无法重现该问题。LoadLibrary
和GetProcAddress
而不是DllImport
和static extern ...
。正如其他人所指出的,这是由于.NET垃圾收集了实际的委托。这是.NET p / Invoke的一个常见问题。
具体来说,这段代码:
set_callback(Callback);
实际上是此代码的语法糖:
set_callback(new callback_t(Callback));
如你所见,callback_t
实例实际上并没有保存在任何地方。因此,set_callback
退货后,它不再扎根并且有资格使用GC。
最简单的解决方案是将其保存到一个有根的变量中,直到C ++代码不再引用它为止:
static async Task Main(string[] args)
{
_callback = Callback;
set_callback(_callback);
while (!Console.KeyAvailable)
{
run();
GC.Collect();
await Task.Delay(1);
}
}
private static callback_t _callback;
请注意,将此同步或更改Task.Delay
为0
将会删除Task
最终导致GC的分配,从而释放了委托。