You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Concurrent raises of one external event lose all but the last #131
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
Create one AsyncRequestExternalEvent<int, int> whose handler returns its argument.
Outside the Revit API context, call RaiseAsync(1) and then RaiseAsync(2), before Revit runs the event.
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.
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.
Activity
Nice3point commentedon Oct 23, 2026
Decision
DeniedorTimedOutnever runs.Raise(argument)returns that result, and theRaiseAsynctask fails with anInvalidOperationException.