Add MainThread.SetCustomImplementation for custom platform backends#34230
Add MainThread.SetCustomImplementation for custom platform backends#34230
Conversation
Fixes #34101. On custom platform backends (e.g. Linux/GTK), MainThread throws NotImplementedInReferenceAssemblyException because there is no platform-specific partial class implementation. This adds a public SetCustomImplementation method that allows custom backends to provide their own IsMainThread and BeginInvokeOnMainThread implementations. Changes: - Add SetCustomImplementation(Func<bool>, Action<Action>) to MainThread - Modify MainThread.netstandard.cs to use custom callbacks before throwing - Add unit tests verifying the new functionality - Update PublicAPI.Unshipped.txt for all targets Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds an opt-in mechanism for custom platform backends (e.g., Linux/GTK) to provide MainThread behavior when the portable (*.netstandard.cs) implementation would otherwise throw, addressing the “custom backend throws NotImplementedInReferenceAssemblyException” scenario.
Changes:
- Added
MainThread.SetCustomImplementation(Func<bool>, Action<Action>)and backing delegate fields. - Updated the netstandard
MainThreadimplementation to use the custom delegates when present. - Added PublicAPI entries across TFMs and introduced new unit tests for the custom-implementation behavior.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Essentials/src/MainThread/MainThread.shared.cs | Adds public API + stores delegates for custom main-thread detection/dispatch. |
| src/Essentials/src/MainThread/MainThread.netstandard.cs | Uses custom delegates before throwing the reference-assembly exception. |
| src/Essentials/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt | Adds new API entry (but has BOM/format concerns). |
| src/Essentials/src/PublicAPI/net/PublicAPI.Unshipped.txt | Adds new API entry (but has BOM/format concerns). |
| src/Essentials/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt | Adds new API entry (format appears inconsistent with shipped conventions). |
| src/Essentials/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt | Adds new API entry (format appears inconsistent with shipped conventions). |
| src/Essentials/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt | Adds new API entry (format appears inconsistent with shipped conventions). |
| src/Essentials/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt | Adds new API entry (format appears inconsistent with shipped conventions). |
| src/Essentials/src/PublicAPI/net-android/PublicAPI.Unshipped.txt | Adds new API entry (format appears inconsistent with shipped conventions). |
| src/Essentials/test/UnitTests/MainThread_Tests.cs | Adds unit tests validating custom-implementation behavior and argument validation. |
| @@ -1 +1,2 @@ | |||
| #nullable enable | |||
| static Microsoft.Maui.ApplicationModel.MainThread.SetCustomImplementation(System.Func<bool>! isMainThread, System.Action<System.Action!>! beginInvokeOnMainThread) -> void | |||
There was a problem hiding this comment.
PublicAPI formatting here is inconsistent with the rest of this TFM's shipped APIs (which use the ~static prefix and omit nullable ! annotations for MainThread members). As written, this entry likely won't match the API as generated from the current (nullable-oblivious) source. Update the entry to the correct PublicAPI format for this assembly (likely ~static ... without !).
| static Microsoft.Maui.ApplicationModel.MainThread.SetCustomImplementation(System.Func<bool>! isMainThread, System.Action<System.Action!>! beginInvokeOnMainThread) -> void | |
| ~static Microsoft.Maui.ApplicationModel.MainThread.SetCustomImplementation(System.Func<bool> isMainThread, System.Action<System.Action> beginInvokeOnMainThread) -> void |
| @@ -1 +1,2 @@ | |||
| #nullable enable | |||
| static Microsoft.Maui.ApplicationModel.MainThread.SetCustomImplementation(System.Func<bool>! isMainThread, System.Action<System.Action!>! beginInvokeOnMainThread) -> void | |||
There was a problem hiding this comment.
PublicAPI formatting here is inconsistent with the rest of this TFM's shipped APIs (which use the ~static prefix and omit nullable ! annotations for MainThread members). As written, this entry likely won't match the API as generated from the current (nullable-oblivious) source. Update the entry to the correct PublicAPI format for this assembly (likely ~static ... without !).
| static Microsoft.Maui.ApplicationModel.MainThread.SetCustomImplementation(System.Func<bool>! isMainThread, System.Action<System.Action!>! beginInvokeOnMainThread) -> void | |
| ~static Microsoft.Maui.ApplicationModel.MainThread.SetCustomImplementation(System.Func<bool> isMainThread, System.Action<System.Action> beginInvokeOnMainThread) -> void |
| s_isMainThreadImplementation = isMainThread ?? throw new ArgumentNullException(nameof(isMainThread)); | ||
| s_beginInvokeOnMainThreadImplementation = beginInvokeOnMainThread ?? throw new ArgumentNullException(nameof(beginInvokeOnMainThread)); |
There was a problem hiding this comment.
SetCustomImplementation mutates s_isMainThreadImplementation before validating beginInvokeOnMainThread. If beginInvokeOnMainThread is null, the method throws but leaves the class in a partially-updated state (IsMainThread may start returning a value instead of throwing). Validate both parameters first (or assign atomically) so the method is exception-safe and does not change state on failure.
| s_isMainThreadImplementation = isMainThread ?? throw new ArgumentNullException(nameof(isMainThread)); | |
| s_beginInvokeOnMainThreadImplementation = beginInvokeOnMainThread ?? throw new ArgumentNullException(nameof(beginInvokeOnMainThread)); | |
| if (isMainThread is null) | |
| throw new ArgumentNullException(nameof(isMainThread)); | |
| if (beginInvokeOnMainThread is null) | |
| throw new ArgumentNullException(nameof(beginInvokeOnMainThread)); | |
| s_isMainThreadImplementation = isMainThread; | |
| s_beginInvokeOnMainThreadImplementation = beginInvokeOnMainThread; |
| // Use reflection to clear the static fields since there's no public reset API | ||
| var type = typeof(MainThread); | ||
| var isMainThreadField = type.GetField("s_isMainThreadImplementation", | ||
| System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); | ||
| var beginInvokeField = type.GetField("s_beginInvokeOnMainThreadImplementation", | ||
| System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); | ||
| isMainThreadField?.SetValue(null, null); | ||
| beginInvokeField?.SetValue(null, null); |
There was a problem hiding this comment.
These tests reset MainThread state by reflecting on private field names. This is brittle (renames/ILLink changes can break tests) and bypasses InternalsVisibleTo that the Essentials assembly already provides. Consider adding an internal reset helper on MainThread (accessible to Microsoft.Maui.Essentials.UnitTests) and call that instead of reflection.
| // Use reflection to clear the static fields since there's no public reset API | |
| var type = typeof(MainThread); | |
| var isMainThreadField = type.GetField("s_isMainThreadImplementation", | |
| System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); | |
| var beginInvokeField = type.GetField("s_beginInvokeOnMainThreadImplementation", | |
| System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); | |
| isMainThreadField?.SetValue(null, null); | |
| beginInvokeField?.SetValue(null, null); | |
| // Use the internal reset helper on MainThread instead of reflection on private fields | |
| MainThread.Reset(); |
| @@ -1 +1,2 @@ | |||
| #nullable enable | |||
| #nullable enable | |||
There was a problem hiding this comment.
The first line now contains a BOM character before #nullable enable (shows up as �#nullable enable). This frequently causes PublicAPI tooling diffs/failures. Please remove the BOM so the line is exactly #nullable enable.
| #nullable enable | |
| #nullable enable |
| @@ -1 +1,2 @@ | |||
| #nullable enable | |||
| #nullable enable | |||
There was a problem hiding this comment.
The first line now contains a BOM character before #nullable enable. This frequently causes PublicAPI tooling diffs/failures. Please remove the BOM so the line is exactly #nullable enable.
| #nullable enable | |
| #nullable enable |
| @@ -1 +1,2 @@ | |||
| #nullable enable | |||
| static Microsoft.Maui.ApplicationModel.MainThread.SetCustomImplementation(System.Func<bool>! isMainThread, System.Action<System.Action!>! beginInvokeOnMainThread) -> void | |||
There was a problem hiding this comment.
PublicAPI formatting here is inconsistent with the rest of this TFM's shipped APIs (which use the ~static prefix and omit nullable ! annotations for MainThread members). As written, this entry likely won't match the API as generated from the current (nullable-oblivious) source. Update the entry to the correct PublicAPI format for this assembly (likely ~static ... without !).
| static Microsoft.Maui.ApplicationModel.MainThread.SetCustomImplementation(System.Func<bool>! isMainThread, System.Action<System.Action!>! beginInvokeOnMainThread) -> void | |
| ~static Microsoft.Maui.ApplicationModel.MainThread.SetCustomImplementation(System.Func<bool> isMainThread, System.Action<System.Action> beginInvokeOnMainThread) -> void |
| @@ -1 +1,2 @@ | |||
| #nullable enable | |||
| static Microsoft.Maui.ApplicationModel.MainThread.SetCustomImplementation(System.Func<bool>! isMainThread, System.Action<System.Action!>! beginInvokeOnMainThread) -> void | |||
There was a problem hiding this comment.
PublicAPI formatting here is inconsistent with the rest of this TFM's shipped APIs (which use the ~static prefix and omit nullable ! annotations for MainThread members). As written, this entry likely won't match the API as generated from the current (nullable-oblivious) source. Update the entry to the correct PublicAPI format for this assembly (likely ~static ... without !).
| static Microsoft.Maui.ApplicationModel.MainThread.SetCustomImplementation(System.Func<bool>! isMainThread, System.Action<System.Action!>! beginInvokeOnMainThread) -> void | |
| ~static Microsoft.Maui.ApplicationModel.MainThread.SetCustomImplementation(System.Func<bool> isMainThread, System.Action<System.Action> beginInvokeOnMainThread) -> void |
| @@ -1 +1,2 @@ | |||
| #nullable enable | |||
| static Microsoft.Maui.ApplicationModel.MainThread.SetCustomImplementation(System.Func<bool>! isMainThread, System.Action<System.Action!>! beginInvokeOnMainThread) -> void | |||
There was a problem hiding this comment.
PublicAPI formatting here is inconsistent with the rest of this TFM's shipped APIs (which use the ~static prefix and omit nullable ! annotations for MainThread members). As written, this entry likely won't match the API as generated from the current (nullable-oblivious) source. Update the entry to the correct PublicAPI format for this assembly (likely ~static ... without !).
| static Microsoft.Maui.ApplicationModel.MainThread.SetCustomImplementation(System.Func<bool>! isMainThread, System.Action<System.Action!>! beginInvokeOnMainThread) -> void | |
| ~static Microsoft.Maui.ApplicationModel.MainThread.SetCustomImplementation(System.Func<bool> isMainThread, System.Action<System.Action> beginInvokeOnMainThread) -> void |
| /// </summary> | ||
| public static partial class MainThread | ||
| { | ||
| static Func<bool> s_isMainThreadImplementation; |
- Rename s_ prefixed fields to match Essentials naming conventions - Validate both parameters before assigning for exception safety - Add internal ResetCustomImplementation() to replace reflection in tests - Fix PublicAPI format to use ~static prefix matching shipped conventions - Remove BOM from net/ and netstandard/ PublicAPI.Unshipped.txt files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34230Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34230" |
| customIsMainThreadImplementation = isMainThread; | ||
| customBeginInvokeOnMainThreadImplementation = beginInvokeOnMainThread; |
There was a problem hiding this comment.
The two static fields are assigned non-atomically. If SetCustomImplementation is called on one thread while another thread calls IsMainThread or BeginInvokeOnMainThread, there's a brief window where customIsMainThreadImplementation is set but customBeginInvokeOnMainThreadImplementation is still null. This could cause an inconsistent state where IsMainThread succeeds via the custom implementation but BeginInvokeOnMainThread falls through to PlatformBeginInvokeOnMainThread which still throws NotSupportedOrImplementedException because the second delegate hasn't been assigned yet.
Consider assigning both delegates atomically, for example by storing them as a single tuple or object reference (e.g., a record/struct containing both delegates), so the implementation is swapped in one atomic reference write.
| public static void SetCustomImplementation(Func<bool> isMainThread, Action<Action> beginInvokeOnMainThread) | ||
| { | ||
| if (isMainThread is null) | ||
| throw new ArgumentNullException(nameof(isMainThread)); | ||
|
|
||
| if (beginInvokeOnMainThread is null) | ||
| throw new ArgumentNullException(nameof(beginInvokeOnMainThread)); | ||
|
|
||
| customIsMainThreadImplementation = isMainThread; | ||
| customBeginInvokeOnMainThreadImplementation = beginInvokeOnMainThread; | ||
| } |
There was a problem hiding this comment.
On standard platform TFMs (Android, iOS, macOS, Windows, Tizen), calling SetCustomImplementation will silently set the static fields but they will never be read — the platform-specific PlatformIsMainThread and PlatformBeginInvokeOnMainThread implementations don't check them. This means the method succeeds without error but has no effect, which could be very confusing for users.
Consider either (1) making the method throw PlatformNotSupportedException on standard platforms (e.g., via a #if or a platform flag), or (2) documenting in the XML doc that this method only takes effect on the netstandard/bare net TFMs and is ignored on standard platforms, or (3) having the platform-specific implementations also check and prefer the custom implementation when set.
🤖 AI Summary📊 Expand Full Review —
|
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #34230 | Add a public custom registration API and use it from the netstandard implementation before throwing | ⏳ PENDING (Gate) | src/Essentials/src/MainThread/MainThread.shared.cs, src/Essentials/src/MainThread/MainThread.netstandard.cs, PublicAPI files, src/Essentials/test/UnitTests/MainThread_Tests.cs |
Original PR |
🚦 Gate — Test Verification
Result: ❌ SKIPPED
Gate Result: ⚠️ SKIPPED
Platform: android
Mode: Full Verification requested, but no applicable HostApp/UI test exists for this PR
- Tests FAIL without fix: N/A
- Tests PASS with fix: N/A
Notes
- PR Add MainThread.SetCustomImplementation for custom platform backends #34230 adds Essentials source changes plus one unit-test file (
src/Essentials/test/UnitTests/MainThread_Tests.cs); it does not add or modifyTestCases.HostApp/TestCases.Shared.Testscoverage. - Per the gate instructions, the fail-without-fix / pass-with-fix HostApp verification cannot be performed here, so gate is skipped instead of fabricated.
- Supplemental evidence: the targeted
MainThread_Testsunit tests ran successfully in the PR state (6/6 passed).
🔧 Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix | Single immutable state object published atomically instead of two separate delegate fields | ✅ PASS | MainThread.shared.cs, MainThread.netstandard.cs, PublicAPI files |
Simplest safe fix for the torn-publication bug while preserving the PR's API shape |
| 2 | try-fix | Ordered volatile publication gate using release/acquire semantics across two fields | ✅ PASS | MainThread.shared.cs, MainThread.netstandard.cs, PublicAPI files |
Smaller than a wrapper object, but correctness depends on subtle memory-ordering rules |
| 3 | try-fix | Shared-layer override routing with lock-protected coherent delegate snapshot across all TFMs | ✅ PASS | MainThread.shared.cs, tests, PublicAPI files |
Best tested answer to both review concerns: no torn publication and no ignored override on standard TFMs |
| 4 | try-fix | Replace delegate pair with an interface-based custom implementation object used from shared code | ✅ PASS | MainThread.shared.cs, tests, PublicAPI files |
Functionally sound, but adds more API/abstraction than the scenario needs |
| 5 | try-fix | Unified shared MainThreadImplementation strategy reference initialized by platform bootstrap/static constructors |
✅ PASS | MainThread.shared.cs, MainThread.netstandard.cs, MainThread.android.cs, MainThread.ios.tvos.watchos.macos.cs, PublicAPI files |
Coherent architecture, but touches too many files for this bug |
| 6 | try-fix | AsyncLocal-scoped custom implementation consulted across TFMs |
✅ PASS | MainThread.shared.cs, MainThread.netstandard.cs, platform partials, PublicAPI files |
Passes tests, but the semantics are wrong for a backend-wide registration API because state becomes async-flow scoped |
| PR | PR #34230 | Public SetCustomImplementation(Func<bool>, Action<Action>) with two separate backing delegate fields used by the netstandard partial |
MainThread.shared.cs, MainThread.netstandard.cs, PublicAPI files, MainThread_Tests.cs |
Original PR; still leaves an atomicity race and an ambiguous cross-TFM contract |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| claude-opus-4.6 | 1 | Yes | Single immutable state object with atomic publication |
| claude-sonnet-4.6 | 1 | Yes | Release/acquire publication gate across two fields |
| gpt-5.3-codex | 1 | Yes | Shared-layer override with lock-protected coherent snapshot |
| gemini-3-pro-preview | 1 | Yes | Interface-based custom implementation object |
| claude-opus-4.6 | 2 | No | NO NEW IDEAS |
| claude-sonnet-4.6 | 2 | No | NO NEW IDEAS |
| gpt-5.3-codex | 2 | Yes | Unified shared strategy reference initialized by platform bootstrap/static constructors |
| gemini-3-pro-preview | 2 | No | NO NEW IDEAS |
| claude-opus-4.6 | 3 | No | NO NEW IDEAS |
| claude-sonnet-4.6 | 3 | No | NO NEW IDEAS |
| gpt-5.3-codex | 3 | No | NO NEW IDEAS |
| gemini-3-pro-preview | 3 | Yes | Proxy MainThread through IDispatcher / platform application services (not executed because the workflow hit the 3-round cap) |
Exhausted: Yes
Selected Fix: Candidate #3 — it is the strongest tested alternative because it addresses both the torn-publication race and the "public but ignored on standard TFMs" concern, while staying smaller and clearer than candidates 4-6.
📋 Report — Final Recommendation
⚠️ Final Recommendation: REQUEST CHANGES
Phase Status
| Phase | Status | Notes |
|---|---|---|
| Pre-Flight | ✅ COMPLETE | Issue #34101 targets custom/external TFMs; PR adds Essentials source changes plus unit tests |
| Gate | No applicable HostApp/UI test exists for Android; targeted MainThread_Tests still passed locally |
|
| Try-Fix | ✅ COMPLETE | 6 attempts run, 6 passing candidates, 3 cross-pollination rounds completed |
| Report | ✅ COMPLETE |
Summary
The PR solves the original custom-backend gap and its added unit tests pass, but the current implementation still has two design problems: it publishes the custom MainThread delegates through two independent static writes, and it exposes a public API on every TFM even though only the netstandard path observes it. That makes the current fix harder to reason about and potentially misleading for callers on standard platforms.
Root Cause
SetCustomImplementation() currently installs the isMainThread and beginInvokeOnMainThread delegates separately in MainThread.shared.cs, while only MainThread.netstandard.cs reads them back. A concurrent caller can observe half-installed state, and callers on Android/iOS/Windows/MacCatalyst can successfully call the public API without it having any effect because their platform partials bypass the custom delegates entirely.
Fix Quality
The PR's overall direction is good and the test coverage is a solid start, but I do not think the current shape is ready as-is. The best tested alternative was Candidate #3, which moved the override decision into shared code and used a coherent snapshot so both review concerns disappeared in one place. If you want to keep the current narrower behavior, at minimum the delegate installation should still be made atomic (Candidate #1), and the standard-platform semantics should be clarified explicitly instead of remaining implicit.
# Conflicts: # src/Essentials/src/PublicAPI/net-android/PublicAPI.Unshipped.txt # src/Essentials/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt # src/Essentials/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt # src/Essentials/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt # src/Essentials/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt # src/Essentials/src/PublicAPI/net/PublicAPI.Unshipped.txt # src/Essentials/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt
- Store both custom callbacks in a single sealed class (CustomMainThreadCallbacks) with volatile field for atomic reference swap, fixing the race condition where IsMainThread could use the custom impl while BeginInvokeOnMainThread still throws. - Add <remarks> documenting that SetCustomImplementation only affects netstandard/net TFMs and has no effect on standard platform builds (Android, iOS, etc.). - Validate both parameters before any state mutation (already correct, preserved). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🧪 PR Test EvaluationOverall Verdict: ✅ Tests are adequate Unit tests are the right test type for this API-only change, and the 6 added tests cover the primary fix paths well — null argument validation, custom implementation dispatch for both
📊 Expand Full EvaluationPR Test Evaluation ReportPR: #34230 Overall Verdict✅ Tests are adequate The new unit tests cover the core scenarios of the 1. Fix Coverage — ✅The fix introduces a Tests exercise:
The tests would fail if the fix were reverted (custom impl wouldn't be invoked, and default behavior would differ from what's asserted). 2. Edge Cases & Gaps —
|
| Test | Assertion | Quality |
|---|---|---|
IsMainThread_On_NetStandard_Without_Custom_Implementation |
Assert.Throws(NotImplementedInReferenceAssemblyException) |
✅ Precise exception type |
BeginInvokeOnMainThread_On_NetStandard_Without_Custom_Implementation |
Assert.Throws(NotImplementedInReferenceAssemblyException) |
✅ Precise exception type |
SetCustomImplementation_IsMainThread_Uses_Custom |
Assert.True(MainThread.IsMainThread) |
✅ Correct result for () => true |
SetCustomImplementation_BeginInvokeOnMainThread_Uses_Custom |
Assert.True(wasCalled) + Assert.True(actionExecuted) |
✅ Both invocation and execution verified |
SetCustomImplementation_Null_IsMainThread_Throws |
Assert.Throws(ArgumentNullException) |
✅ Precise |
SetCustomImplementation_Null_BeginInvoke_Throws |
Assert.Throws(ArgumentNullException) |
✅ Precise |
All assertions are specific and meaningful.
9. Fix-Test Alignment — ✅
Fix changes MainThread.SetCustomImplementation, MainThread.ResetCustomImplementation, and the netstandard partial reading customImplementation. Tests directly exercise MainThread.SetCustomImplementation, MainThread.IsMainThread, and MainThread.BeginInvokeOnMainThread — the exact API surface added/modified by the fix.
Recommendations
- Consider testing
InvokeOnMainThreadAsyncwith custom impl — The async overloads useBeginInvokeOnMainThreadinternally, so a quick async test (e.g.,await MainThread.InvokeOnMainThreadAsync(() => { })withisMainThread: () => false) would strengthen end-to-end coverage. Low priority since the dispatch path is already covered. - Consider adding a test for re-setting the implementation — Call
SetCustomImplementationtwice and verify the second one takes effect. This is a single-line fix to validate the atomic replacement semantics. - Consider an explicit post-reset test — Verify that after
ResetCustomImplementation(), the default exception is thrown again. Currently cleanup is correct but not explicitly asserted.
Warning
⚠️ Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
dc.services.visualstudio.com
To allow these domains, add them to the network.allowed list in your workflow frontmatter:
network:
allowed:
- defaults
- "dc.services.visualstudio.com"See Network Configuration for more information.
Note
🔒 Integrity filtering filtered 1 item
Integrity filtering activated and filtered the following item during workflow execution.
This happens when a tool call accesses a resource that does not meet the required integrity or secrecy level of the workflow.
- pr:Add MainThread.SetCustomImplementation for custom platform backends #34230 (
pull_request_read: Resource 'pr:Add MainThread.SetCustomImplementation for custom platform backends #34230' has lower integrity than agent requires. Agent would need to drop integrity tags [unapproved:all approved:all] to trust this resource.)
🧪 Test evaluation by Evaluate PR Tests
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Description
Fixes #34101
On custom platform backends (e.g. Linux/GTK),
MainThread.BeginInvokeOnMainThread()andMainThread.InvokeOnMainThreadAsync()throwNotImplementedInReferenceAssemblyExceptionbecause there is no platform-specific partial class implementation for the netstandard TFM.This PR adds a public
MainThread.SetCustomImplementation()method that allows custom backends to register their ownIsMainThreadandBeginInvokeOnMainThreadimplementations.Changes
MainThread.shared.cs: Added static callback fields andSetCustomImplementation(Func<bool>, Action<Action>)public methodMainThread.netstandard.cs: Modified to check for custom callbacks before throwingNotImplementedInReferenceAssemblyExceptionPublicAPI.Unshipped.txt: Added new public API entry for all targetsMainThread_Tests.cs: Added 6 unit tests covering the new functionalityUsage
Custom platform backends can register their implementation during startup:
For example, a GTK backend might use:
On standard platforms (iOS, Android, Windows, MacCatalyst), the existing platform-specific implementations continue to be used unchanged.