{"id":109056,"date":"2023-11-23T07:00:00","date_gmt":"2023-11-23T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109056"},"modified":"2023-11-23T07:15:00","modified_gmt":"2023-11-23T15:15:00","slug":"20231123-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20231123-00\/?p=109056","title":{"rendered":"On the need to keep most event sources alive if you want them to raise events"},"content":{"rendered":"<p>A customer was having difficulty registering for the Windows Runtime <code>Radio.<wbr \/>StateChanged<\/code> which notifies you that the state of a radio (such as a cellular radio or WiFi radio) has changed. They were able to narrow it down to a small program:<\/p>\n<pre>#include &lt;stdio.h&gt;\r\n#include &lt;winrt\/Windows.Devices.Radios.h&gt;\r\n#include &lt;winrt\/Windows.Foundation.h&gt;\r\n#include &lt;winrt\/Windows.Foundation.Collections.h&gt;\r\n\r\nusing namespace winrt;\r\nusing namespace winrt::Windows::Devices::Radios;\r\nusing namespace winrt::Windows::Foundation;\r\nusing namespace winrt::Windows::Foundation::Collections;\r\n\r\nvoid OnRadioStateChanged(Radio const&amp; sender, IInspectable const&amp; args)\r\n{\r\n    printf(\"Radio state changed: %ls\\n\", sender.Name().c_str());\r\n}\r\n\r\nvoid RegisterRadios()\r\n{\r\n    auto radios = Radio::GetRadiosAsync().get();\r\n    for (auto radio : radios) {\r\n        radio.StateChanged(&amp;OnRadioStateChanged);\r\n        printf(\"Registered event on radio %ls\\n\", radio.Name().c_str());\r\n    }\r\n\r\n}\r\n\r\nint main()\r\n{\r\n    init_apartment();\r\n    RegisterRadios();\r\n\r\n    std::cout &lt;&lt; \"Waiting for radios to change\\n\";\r\n    getchar();\r\n}\r\n<\/pre>\n<p>They found that the program registered its events successfully, but the event handler was never called.<\/p>\n<p>The problem is that they registered the events on the <code>Radio<\/code> objects that were returned by <code>Get\u00adRadios\u00adAsync()<\/code>, but then allowed those references to <code>Radio<\/code> objects to destruct.<\/p>\n<p>As a general rule, when a program releases its last reference to a Windows Runtime object, the Windows Runtime object destructs, and that means it won&#8217;t generate any events.<\/p>\n<p>There are exceptions to this principle. For example, there may be other references to the object being held by components outside the program itself. XAML elements, for example, remain alive when they are part of a visual tree because the XAML parent object has a reference to the XAML element child. And DispatcherTimer objects retain a reference to themselves as long as they are still ticking. Another category of objects that are kept alive externally are those of the form <code>T.Get\u00adFor\u00adCurrent\u00adView()<\/code>, which are per-view singleton objects which remain valid as long as the view remains valid.<\/p>\n<p>Now, in garbage-collected languages, the exact point that a reference is run down by the garbage collector is generally unpredictable. Consider the equivalent C# program:<\/p>\n<pre>using System;\r\nusing Windows.Devices.Radios;\r\n\r\nclass Program\r\n{\r\n    static void OnRadioStateChanged(Radio sender, object args)\r\n    {\r\n        Console.WriteLine($\"Radio state changed: {sender.Name}\");\r\n    }\r\n\r\n    static void RegisterRadios()\r\n    {\r\n        var radios = Radio.GetRadiosAsync().Result;\r\n        foreach (var radio in radios) {\r\n            radio.StateChanged += OnRadioStateChanged;\r\n            Console.WriteLine($\"Registered event on radio {radio.Name}\");\r\n        }\r\n    }\r\n\r\n    public static void Main()\r\n    {\r\n        RegisterRadios();\r\n\r\n        Console.WriteLine($\"Waiting for radios to change\");\r\n        Console.ReadLine();\r\n    }\r\n}\r\n<\/pre>\n<p>Those <code>Radio<\/code> objects that were obtained by the <code>Register\u00adRadios<\/code> method are going to be released by the garbage collector at some unspecific point in the future, and the effect on your program is going to be that it seems to be working for a while, and then suddenly stops receiving radio events.<\/p>\n<p>You need to keep the <code>Radio<\/code> objects alive if you intend to receive events from them.<\/p>\n<pre>#include &lt;stdio.h&gt;\r\n#include &lt;winrt\/Windows.Devices.Radios.h&gt;\r\n#include &lt;winrt\/Windows.Foundation.h&gt;\r\n#include &lt;winrt\/Windows.Foundation.Collections.h&gt;\r\n\r\nusing namespace winrt;\r\nusing namespace winrt::Windows::Devices::Radios;\r\nusing namespace winrt::Windows::Foundation;\r\nusing namespace winrt::Windows::Foundation::Collections;\r\n\r\nvoid OnRadioStateChanged(Radio const&amp; sender, IInspectable const&amp; args)\r\n{\r\n    printf(\"Radio state changed: %ls\\n\", sender.Name().c_str());\r\n}\r\n\r\n<span style=\"border: solid 1px currentcolor;\">auto<\/span> RegisterRadios()\r\n{\r\n    auto radios = Radio::GetRadiosAsync().get();\r\n    for (auto radio : radios) {\r\n        radio.StateChanged(&amp;OnRadioStateChanged);\r\n        printf(\"Registered event on radio %ls\\n\", radio.Name().c_str());\r\n    }\r\n    <span style=\"border: solid 1px currentcolor;\">return radios<\/span>;\r\n}\r\n\r\nint main()\r\n{\r\n    init_apartment();\r\n    <span style=\"border: solid 1px currentcolor;\">auto radios =<\/span> RegisterRadios();\r\n\r\n    std::cout &lt;&lt; \"Waiting for radios to change\\n\";\r\n    getchar();\r\n}\r\n<\/pre>\n<p>For C#:<\/p>\n<pre>using System;\r\nusing System.Collections.Generic;\r\nusing Windows.Devices.Radios;\r\n\r\nclass Program\r\n{\r\n    static void OnRadioStateChanged(Radio sender, object args)\r\n    {\r\n        Console.WriteLine($\"Radio state changed: {sender.Name}\");\r\n    }\r\n\r\n    static <span style=\"border: solid 1px currentcolor;\">IList&lt;Radio&gt;<\/span> RegisterRadios()\r\n    {\r\n        var radios = Radio.GetRadiosAsync().Result;\r\n        foreach (var radio in radios) {\r\n            radio.StateChanged += OnRadioStateChanged;\r\n            Console.WriteLine($\"Registered event on radio {radio.Name}\");\r\n        }\r\n        <span style=\"border: solid 1px currentcolor;\">return radios<\/span>;\r\n    }\r\n\r\n    public static void Main()\r\n    {\r\n        <span style=\"border: solid 1px currentcolor;\">var radios =<\/span> RegisterRadios();\r\n\r\n        Console.WriteLine($\"Waiting for radios to change\");\r\n        Console.ReadLine();\r\n\r\n        <span style=\"border: solid 1px currentcolor;\">GC.KeepAlive(radios)<\/span>;\r\n    }\r\n}\r\n<\/pre>\n<p>It&#8217;s more common to keep the objects alive by saving them in a member variable of the same class that is handling the events, but in our examples here, we are using static methods rather than instance methods, so we don&#8217;t have that luxury. It&#8217;s either keep them in global variables (boo, global variables) or keep them in local variables whose lifetimes extend beyond the useful lifetimes of the event handlers.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You can&#8217;t receive a notification from something that doesn&#8217;t exist.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-109056","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You can&#8217;t receive a notification from something that doesn&#8217;t exist.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109056","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=109056"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109056\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=109056"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109056"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109056"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}