<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/using-gc-keepalive-in-async-methods-vp64wkwko" -->

---
title: Using GC.KeepAlive in async methods | daily.dev
description: Explains why GC.KeepAlive does not reliably protect objects from garbage collection when used inside async methods, unlike in synchronous code. The root cause...
canonical: https://daily.dev/posts/using-gc-keepalive-in-async-methods-vp64wkwko
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Using GC.KeepAlive in async methods | daily.dev
og:description: Explains why GC.KeepAlive does not reliably protect objects from garbage collection when used inside async methods, unlike in synchronous code. The root cause...
og:url: https://daily.dev/posts/using-gc-keepalive-in-async-methods-vp64wkwko
og:image: https://api.daily.dev/og/posts/Vp64WKWKO.png
og:image:alt: Using GC.KeepAlive in async methods
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Using GC.KeepAlive in async methods

**[Kevin Gosse \(minidump.net\)](https://daily.dev/sources/minidump)** · 9 min read · 0 upvotes · 0 comments

## Summary

Explains why GC.KeepAlive does not reliably protect objects from garbage collection when used inside async methods, unlike in synchronous code. The root cause is traced through how async methods compile into state machines and how the reference chain back to the original object can be broken once the method yields control. A working fix is shown using GCHandle.Alloc to pin the reference until the asynchronous operation completes, along with a discussion of whether this behavior constitutes a bug in the .NET runtime.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://minidump.net/c-using-gc-keepalive-in-async-methods-8d20fd79f0a0>

## Questions this post answers

### Why does GC.KeepAlive not prevent garbage collection when called after an await in an async C# method?

Because the async method is compiled into a state machine, and GC.KeepAlive ends up on a different branch (case) of the state machine's MoveNext method than the native call it's meant to protect, so it only re-references an object the state machine already holds. The real reference chain breaks because the TaskCompletionSource used to resume execution isn't referenced by anything once the async method returns, allowing the delegate to be collected before the native callback fires.

_Developers hitting GC collection bugs in async interop code can compare fixes like this one on daily.dev._

### How do you keep a delegate alive across an async native method call in C#?

Allocate a GCHandle on the delegate before passing it to the native method, then call handle.Free() after the awaited task completes. The GCHandle acts as a special reference that stays alive until explicitly freed, which keeps the delegate (and the TaskCompletionSource and continuation chain it references) alive for the full duration of the asynchronous native operation, unlike a bare GC.KeepAlive call placed after the await.

_Track patterns like this for safe native interop in async C# on daily.dev._

## Similar posts on daily.dev

- [Demystifying async/await in C\#: The Hidden State Machine](https://daily.dev/posts/demystifying-async-await-in-c-the-hidden-state-machine-ej0npqym4) · C\# Corner · 0 upvotes · 0 comments
- [Async or Swim: The Complete Guide to Asynchronous .NET](https://daily.dev/posts/async-or-swim-the-complete-guide-to-asynchronous-net-n2fszfnf6) · ITNEXT · 9 upvotes · 1 comments
- [Threading on Thin Ice: Demystifying the .NET ThreadPool and avoiding starvation](https://daily.dev/posts/threading-on-thin-ice-demystifying-the-net-threadpool-and-avoiding-starvation-9n7wbor0j) · ITNEXT · 8 upvotes · 1 comments

---

Tags: [#.net](https://daily.dev/tags/.net), [#c#](https://daily.dev/tags/c#)

[View this post on daily.dev](https://daily.dev/posts/using-gc-keepalive-in-async-methods-vp64wkwko)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Using GC.KeepAlive in async methods","url":"https://daily.dev/posts/using-gc-keepalive-in-async-methods-vp64wkwko","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/using-gc-keepalive-in-async-methods-vp64wkwko"},"datePublished":"2026-10-04T13:05:15.491Z","dateModified":"2026-10-04T13:09:24.917Z","description":"Explains why GC.KeepAlive does not reliably protect objects from garbage collection when used inside async methods, unlike in synchronous code. The root cause...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/941508d2d5c1723606d14e3fe73c0b51?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/941508d2d5c1723606d14e3fe73c0b51?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Kevin Gosse (minidump.net)","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Kevin Gosse (minidump.net)","logo":"https://media.daily.dev/image/upload/s--8KHn1aFB--/f_auto,q_auto/v1791119046/logos/minidump?_a=BAMAMicg0","url":"https://daily.dev/sources/minidump"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/using-gc-keepalive-in-async-methods-vp64wkwko","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":".net,c#","timeRequired":"PT9M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Kevin Gosse (minidump.net)","item":"https://daily.dev/sources/minidump"},{"@type":"ListItem","position":3,"name":"Using GC.KeepAlive in async methods"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/using-gc-keepalive-in-async-methods-vp64wkwko#faq","mainEntity":[{"@type":"Question","name":"Why does GC.KeepAlive not prevent garbage collection when called after an await in an async C# method?","acceptedAnswer":{"@type":"Answer","text":"Because the async method is compiled into a state machine, and GC.KeepAlive ends up on a different branch (case) of the state machine's MoveNext method than the native call it's meant to protect, so it only re-references an object the state machine already holds. The real reference chain breaks because the TaskCompletionSource used to resume execution isn't referenced by anything once the async method returns, allowing the delegate to be collected before the native callback fires. Developers hitting GC collection bugs in async interop code can compare fixes like this one on daily.dev."}},{"@type":"Question","name":"How do you keep a delegate alive across an async native method call in C#?","acceptedAnswer":{"@type":"Answer","text":"Allocate a GCHandle on the delegate before passing it to the native method, then call handle.Free() after the awaited task completes. The GCHandle acts as a special reference that stays alive until explicitly freed, which keeps the delegate (and the TaskCompletionSource and continuation chain it references) alive for the full duration of the asynchronous native operation, unlike a bare GC.KeepAlive call placed after the await. Track patterns like this for safe native interop in async C# on daily.dev."}}]}
```

