dotnet/runtime

GC.KeepAlive doesn't keep local delegate alive in mono (Xamarin.Android)

Open

#74,966 opened on 2022年9月1日

GitHub で見る
 (24 comments) (0 reactions) (1 assignee)C# (5,445 forks)batch import
api-approvedarea-System.Threading.Taskscode-analyzerhelp wanted

Repository metrics

Stars
 (17,886 stars)
PR merge metrics
 (平均マージ 12d 11h) (30d で 661 merged PRs)

説明

EDITED by @stephentoub on 1/20/2023: The issue at this point tracks an analyzer that would flag use of GC.KeepAlive(local) usage in the context of C# iterators, async methods, and anywhere else the C# compiler does a significant method rewrite such that a "local" isn't actually local, e.g. where that local is lifted to a state machine and the use of GC.KeepAlive has little to no impact, e.g.

object o = new object();
yield return o;
GC.KeepAlive(o);

Description

According to Eric Lippert there is no need to pin a local delegate or keep a GCHandle when passing a delegate via PInvoke as long as you use GC.KeepAlive to keep the delegate alive for as long as the native code could call it. https://stackoverflow.com/a/5465380/78561

However while this seems to be true for the dotnet runtime in mono this does not work and we are getting crashes when the callback happens.

We have found that in mono we need to keep a Normal GCHandle to the delegate GC.KeepAlive does not seem to be enough.

This behaviour needs to be consistent across the mono/dotnet runtimes.

This issue was found when using Xamarin.Android

Reproduction Steps

var taskCompletionSource = new TaskCompletionSource();

MyDelegateType myDelegate = () => taskCompletionSource.SetResult(true);

NativeMethods.MyNativeMethod(myDelegate);

await taskCompletionSource.Task;

GC.KeepAlive(myDelegate);

Expected behavior

GC.KeepAlive should prevent the delegate from being collected until the delegate callback has happened.

Actual behavior

Crash, usually with NullReferenceException

Regression?

No response

Known Workarounds

Get a normal GCHandle to the delegate instead of using GC.KeepAlive

var taskCompletionSource = new TaskCompletionSource();

MyDelegateType myDelegate = () => taskCompletionSource.SetResult(true);

var gcHandle = GCHandle.Alloc(myDelegate, GCHandleType.Normal);

NativeMethods.MyNativeMethod(myDelegate);

await taskCompletionSource.Task;

gcHandle.Free();

Configuration

No response

Other information

No response

コントリビューターガイド