Skip to content

Concurrent raises of one external event lose all but the last #131

Closed

Description

@Nice3point

Every raise of an external event runs its handler with its own argument, and every RaiseAsync task completes.

Several callers share one event: two modeless windows, or a background service and a window.
When a second raise arrives before Revit runs the first, the first argument is lost, and the first RaiseAsync task never completes.

Steps to reproduce

  1. Create one AsyncRequestExternalEvent<int, int> whose handler returns its argument.
  2. Outside the Revit API context, call RaiseAsync(1) and then RaiseAsync(2), before Revit runs the event.
  3. Await both tasks.

✔️ Expected Behavior

  • An event with an argument runs its handler once for every raise, with the argument of that raise, in the order of the raises.
  • An async event runs its handler once for every RaiseAsync, and each task completes with the result or the exception of its own run.
  • In the steps above, the first task completes with 1 and the second with 2.
  • A raise with ExternalEventOptions.AllowDirectInvocation in API mode runs at once, as it does today.
  • ExternalEvent without an argument runs once for the raises that wait, as Revit runs a raised event.

❌ Actual Behavior

  • The handler runs once, with the argument 2.
  • The second task completes with 2.
  • The first task never completes.
  • ExternalEvent<T>.Raise drops the earlier argument the same way, without an error.
Where it happens

ExternalEvent<T>, AsyncExternalEvent, AsyncExternalEvent<T>, AsyncRequestExternalEvent<TResult>, and AsyncRequestExternalEvent<T, TResult> store one argument and one TaskCompletionSource in fields.
Each raise overwrites them, and Revit runs a pending external event once however often it's raised.
The events that the [ExternalEvent] generator emits are built on the same types.

Activity

  1. added
    bug 🐛An unexpected issue that highlights incorrect behavior
    external commands 🚪ExternalCommand, ExternalApplication, and external events
    on Oct 23, 2026
  2. self-assigned this
    on Oct 23, 2026
  3. Nice3point commented on Oct 23, 2026

    @Nice3point
    OwnerAuthor

    Decision

    • A raise that Revit refuses with Denied or TimedOut never runs. Raise(argument) returns that result, and the RaiseAsync task fails with an InvalidOperationException.
    • When Revit refuses to raise the event again for the queued raises, the next queued raise runs in the same event cycle.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bug 🐛An unexpected issue that highlights incorrect behaviorexternal commands 🚪ExternalCommand, ExternalApplication, and external events

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions