-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
Is your feature request related to a problem? Please describe.
Yes. I encountered a "deadlock-like" behavior when using nested Interactions with WPF/Winform's ShowDialog.
Scenario:
- ViewModel A raises Interaction A.
- View A handles it and calls new WindowB().ShowDialog().
- Inside WindowB, ViewModel B raises Interaction B.
- Issue: The handler for Interaction B is never invoked (or is delayed) until WindowB is closed.
When an Interaction Handler synchronously calls a blocking UI method (like window.ShowDialog()), the handler blocks execution before returning the Task to the ReactiveUI infrastructure. This causes the ReactiveUI scheduler/context to remain in a pending state.
As a result, if the modal window (opened by that Interaction) attempts to raise a subsequent/nested Interaction, the new Interaction's handler is never invoked (or is queued indefinitely) until the modal window is closed.
This is a subtle pitfall related to how async/await state machines interact with the message pump when no await occurs before the blocking call.
Why this is a significant issue
While this behavior technically aligns with how the C# async state machine functions (the Task is not returned until the blocking call finishes), it represents a significant design pitfall for consumers of the library.
1.Common Use Case: Triggering a Modal Window via an Interaction is one of the most common use cases for this feature.
2.Counter-intuitive: Developers intuitively expect Interactions to function as independent requests. It is highly counter-intuitive that a blocking call in one handler would silently freeze the scheduling of subsequent, unrelated interactions (specifically those triggered within the new window).
3.Debugging Nightmare: This lack of visibility often leads developers to believe they have encountered a framework bug or a deadlock, resulting in significant time lost to debugging before realizing the issue lies in the Task return timing.
Describe the solution you'd like
I would like to see a Roslyn Analyzer warning or an Info message triggered when a developer registers an Interaction Handler that performs a blocking call (e.g., ShowDialog) without first yielding control.
The warning could suggest ensuring the Task is returned to the scheduler before blocking.
Alternatively, if an Analyzer is too complex to implement for this specific case, a prominent warning in the Interaction Documentation regarding usage with Modal Dialogs would be very helpful.
Describe alternatives you've considered
Currently, the workaround/fix is to manually add await Task.Yield(); (or Task.Delay) at the beginning of the handler. This forces the async state machine to return the Task to ReactiveUI immediately, allowing the framework to process subsequent interactions even while ShowDialog blocks the execution flow inside the handler.
Another alternative is invoking ShowDialog via standard code-behind events, bypassing the Interaction mechanism, but this breaks the MVVM pattern.
Describe suggestions on how to achieve the feature
-
Documentation: Update the Interactions handbook page to include a "Best Practices with Modal Windows" section.
-
Analyzer: Create a rule that detects ShowDialog calls inside a RegisterHandler lambda block and checks if it is preceded by an await.
Additional context
The Problematic Code (Causes nested interactions to hang):
// Parent Interaction Handler
ViewModel.ShowModalInteraction.RegisterHandler(ctx =>
{
var view = new ModalWindow();
// This blocks immediately. The Task is not returned to RxUI context.
// Any Interaction requested INSIDE ModalWindow will hang.
view.ShowDialog();
ctx.SetOutput(true);
}); The Solution (Using Task.Yield):
// Corrected Handler
ViewModel.ShowModalInteraction.RegisterHandler(async ctx =>
{
// Forces the method to return the Task to the caller immediately.
await Task.Yield();
var view = new ModalWindow();
// Now ShowDialog runs safely without blocking the RxUI pipeline.
view.ShowDialog();
ctx.SetOutput(true);
});