At first, I thought it might be some kind of bubble event that we see, so the variable with s , presented as an AutomationElement , was added inside the lambda handler to show whether the second invokation will also select the button (according to the comment @Simon Mourier, result: yes the values ββare identical), and not from its composite label or anything else up or down the visual tree.
After this was ruled out, a closer look at the stacks of calls of the two callbacks revealed something that supported the flow hypothesis. I downloaded UIAComWrapper from git, compiled from the source and debugged with the characters of the source server and turned on.
This is the call stack in the first callback:

It shows that the starting point is the message pump. The core of WndProc washes it out due to the incredibly thick layer of the framework, almost in the reprogramme of all versions of Windows, ever, roughly decoding it as a left mouse button, until it appears in the OnClick() handler of the OnClick() class, from where the signed an automation event rises and heads towards our lamp. So far, nothing unexpected.
And this is the call stack in the second callback:

This shows that the second callback is an artifact of the UIAutomationCore . And: it works in the user thread, and not in the user interface thread. Thus, there is a mechanism that ensures that every thread that subscribes receives a copy, and the user interface thread always does.
Unfortunately, all the arguments that go into lambda are identical for the first and second calls. And comparing call stacks, although possible, would be a solution that is even worse than synchronization / counting events.
But: you can filter events by stream and consume only one of them:
using System; using System.Diagnostics; using System.Threading.Tasks; using System.Windows; using System.Windows.Automation; using System.Windows.Interop; using System.Threading; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs routedEventArgs) { IntPtr windowHandle = new WindowInteropHelper(this).Handle; Task.Run(() => { var element = AutomationElement.FromHandle(windowHandle); Automation.AddAutomationEventHandler(InvokePattern.InvokedEvent, element, TreeScope.Descendants, (s, a) => { var ele = s as AutomationElement; var invokingthread = Thread.CurrentThread; Debug.WriteLine($"Invoked on {invokingthread.ManagedThreadId} for {ele}, event # {a.EventId.Id}"); if (System.Windows.Threading.Dispatcher.FromThread(invokingthread) == null) { Debug.WriteLine("2nd: this is the event we would be waiting for"); } else { Debug.WriteLine("1st: this is the event raised on the UI thread"); } }); }); } private void button_Click(object sender, RoutedEventArgs e) { Debug.WriteLine("Clicked!"); } } }
Result in the output window:
Invoked on 1 for System.Windows.Automation.AutomationElement, event