我有一个C库,它以异步方式执行操作并在提供的回调中返回结果(函数ptr)。说这是其中一项功能:
C代码:
void c_foo(void(*cb)(char*)) {
// spawn a thread
// sleep in the new-thread for sometime
// invoke the callback: cb(some_null_terminated_string)
// exit the thread
}
我还创建了一个类似的C#
代码来测试:
C#代码:
public void TestManaged(Action<string> abcd) {
var thread = new Thread(
() => {
Thread.Sleep(5000);
abcd("Done");
});
thread.Start();
}
正如您所看到的,它会立即将控件返回给调用者,而稍后在由它生成的线程中调用回调。
我正试图从C#
使用此功能但面临问题。我认为不应该被摧毁的物体正在被摧毁。以下是示例代码:
public class Checker {
public string ABCD = Guid.NewGuid().ToString();
~Checker() {
Debug.WriteLine("DESTROYING!!!!");
}
}
public Task<string> Foo() {
return Task.Run(
() => {
var tcs = new TaskCompletionSource<string>();
var a = new Checker(); // --- (0)
Debug.WriteLine("T-ID: Main - " + Thread.CurrentThread.ManagedThreadId);
// Run Either: ---------- (1)
// This DOES NOT work, `a` gets destroyed before the callback is invoked
TestNative(
s => {
Debug.WriteLine("T-ID: CB - " + Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine(a.ABCD);
tcs.SetResult(s);
});
// OR: ------------------ (2)
// This Works
TestManaged((s) =>
{
Debug.WriteLine("T-ID: CB - " + Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine(a.ABCD);
tcs.SetResult(s);
});
return tcs.Task;
});
}
原生C
定义的粘合剂:
public delegate void TestCb(string s);
[DllImport("mydll", EntryPoint = "c_foo")]
public static extern void TestNative(TestCb cb);
最后,我只是将设置调用为:
Debug.WriteLine("Result: " + await Foo());
在上面的代码中,我要么注释掉(1)
并使用(2)
,要么用另一种方式执行,这样只有其中一个执行特定的运行。问题是,我看到在标记为a
的点处创建的对象(0)
,如果通过C
调用(1)
代码,则会被销毁,但在{{1}时仍保留代码是通过C#
调用的,尽管它们都会产生一个线程,并且只在异步一段时间后调用传递的回调。这显然会导致(2)
崩溃。
此外,如果它有用 - 如果(1)
代码没有产生一个线程但是在调用者的(C
)线程中调用了给定的回调,即使使用{{1}也没关系} - 周围的上下文不会被破坏。
为什么会发生这种情况?使用这些C#
库(从它们自己生成的线程调用回调)的方式是什么。
答案 0 :(得分:2)
在.NET中没有自动概念挂起到托管对象的本机方法。您必须告知运行时此类情况。
在你的情况下,这应该这样做:
using System.Runtime.InteropServices;
var callbackHandle = default(GCHandle);
Action<string> nativeCallback = s => {
try {
Debug.WriteLine("T-ID: CB - " + Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine(a.ABCD);
tcs.SetResult(s);
}
finally {
if (callbackHandle.IsAllocated) {
callbackHandle.Free();
}
}
};
callbackHandle = GCHandle.Alloc(nativeCallback);
TestNative(nativeCallback);
您必须在回调上执行此操作,而不仅仅是a
,因为回调本身将在TestNative
返回后进行垃圾回收。在这种情况下,在回调中使用GCHandle
就足够了,因为回调会捕获a
,如果回调可以到达,则a
可以访问。