{"id":54846,"date":"2024-12-10T10:00:00","date_gmt":"2024-12-10T18:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=54846"},"modified":"2024-12-09T20:09:53","modified_gmt":"2024-12-10T04:09:53","slug":"introducing-winforms-async-apis","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/introducing-winforms-async-apis\/","title":{"rendered":"Invoking Async Power: What Awaits WinForms in .NET 9"},"content":{"rendered":"<p>As .NET continues to evolve, so do the tools available to WinForms developers,\nenabling more efficient and responsive applications. With .NET 9, we\u2019re excited\nto introduce a collection of new asynchronous APIs that significantly streamline\nUI management tasks. From updating controls to showing forms and dialogs, these\nadditions bring the power of async programming to WinForms in new ways. In this\npost, we\u2019ll dive into four key APIs, explaining how they work, where they shine,\nand how to start using them.<\/p>\n<h2>Meet the New Async APIs<\/h2>\n<p>.NET 9 introduces several async APIs designed specifically for WinForms, making\nUI operations more intuitive and performant in asynchronous scenarios. The new\nadditions include:<\/p>\n<ul>\n<li><strong>Control.InvokeAsync<\/strong> \u2013 Fully released in .NET 9, this API helps marshal\ncalls to the UI thread asynchronously.<\/li>\n<li><strong>Form.ShowAsync<\/strong> and <strong>Form.ShowDialogAsync<\/strong> (Experimental) \u2013 These APIs\nlet developers show forms asynchronously, making life easier in complex UI\nscenarios.<\/li>\n<li><strong>TaskDialog.ShowDialogAsync<\/strong> (Experimental) \u2013 This API provides a way to\nshow Task-Dialog-based message box dialogs asynchronously, which is especially\nhelpful for long-running, UI-bound operations.<\/li>\n<\/ul>\n<p>Let&#8217;s break down each set of APIs, starting with <code>InvokeAsync<\/code>.<\/p>\n<h3>Control.InvokeAsync: Seamless Asynchronous UI Thread Invocation<\/h3>\n<p><code>InvokeAsync<\/code> offers a powerful way to marshal calls to the UI thread without\nblocking the calling thread. The method lets you execute both synchronous and\nasynchronous callbacks on the UI thread, offering flexibility while preventing\naccidental \u201cfire-and-forget\u201d behavior. It does that by queueing operations in\nthe WinForms main message queue, ensuring they\u2019re executed on the UI thread.\nThis behavior is similar to <code>Control.Invoke<\/code>, which also marshals calls to\nthe UI thread, but there\u2019s an important difference: <code>InvokeAsync<\/code> doesn\u2019t block\nthe calling thread because it <em>posts<\/em> the delegate to the message queue, rather\nthan <em>sending<\/em> it.<\/p>\n<h3>Wait &#8211; Sending vs. Posting? Message Queue?<\/h3>\n<p>Let\u2019s break down these concepts to clarify what they mean and why\n<code>InvokeAsync<\/code>&#8216;s approach can help improve app responsiveness.<\/p>\n<p>In WinForms, all UI operations happen on the main UI thread. To manage these\noperations, the UI thread runs a loop, known as the message loop, which\ncontinually processes messages\u2014like button clicks, screen repaints, and other\nactions. This loop is the heart of how WinForms stays responsive to user actions\nwhile processing instructions. When you are working with modern APIs, the\nmajority of your App&#8217;s code does not run on this UI thread. Ideally, the UI\nthread should only be used to do those things which are necessary to update\nyour UI. There are situations when your code doesn&#8217;t end up on\nthe UI Thread automatically. One example is when you\nspin-up a dedicated task to perform a\ncompute-intense operation in parallel.\nIn these cases, you need to &#8220;marshal&#8221; the code execution to the UI thread, so that\nthe UI thread then can update the UI. Because otherwise it&#8217;s this:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2024\/12\/CrossThreadException1.png\" alt=\"Screenshot of a typical Cross-Thread-Exception in the Debugger\" \/><\/p>\n<p>Let&#8217;s say I am not allowed to go into a certain room to get a glass of\nmilk, but you are. In that case, there is only one option: Since I cannot become\nyou, I can only ask you to get me that glass of milk. And that&#8217;s the same with\nthread marshalling. A worker thread cannot become the UI thread. But the\nexecution of code (the getting of the glass of milk) can be marshalled. In other\nwords: the worker thread can ask the UI Thread to execute some code on its\nbehalf. And, simply put, that works by queuing the delegate of a method into the message\nqueue.<\/p>\n<p>And with that, lets address this <em>Sending<\/em> and <em>Posting<\/em> confusion: You have two\nmain ways to queue up actions in this loop:<\/p>\n<p><strong>Sending a Message (Blocking):<\/strong> <code>Control.Invoke<\/code> uses this approach. When you\ncall <code>Control.Invoke<\/code>, it synchronously sends the specified delegate to the UI\nthread\u2019s message queue. This action is blocking, meaning the calling thread\nwaits until the UI thread processes the delegate before continuing. This is\nuseful when the calling code depends on an immediate result from the UI thread\nbut can lead to UI freezes if overused, especially during long-running\noperations.<\/p>\n<p><strong>Posting a Message (Non-Blocking):<\/strong> <code>InvokeAsync<\/code> posts the delegate to the\nmessage queue, which is a non-blocking operation. This approach tells the UI\nthread to queue up the action and handle it as soon as it can, but the calling\nthread doesn\u2019t wait around for it to finish. The method returns immediately,\nallowing the calling thread to continue its work. This distinction is\nparticularly valuable in async scenarios, as it allows the app to handle other\ntasks without delay, minimizing UI thread bottlenecks.<\/p>\n<p>Here\u2019s a quick comparison:<\/p>\n<table>\n<thead>\n<tr>\n<th>Operation<\/th>\n<th>Method<\/th>\n<th>Blocking<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>Send<\/strong><\/td>\n<td><code>Control.Invoke<\/code><\/td>\n<td>Yes<\/td>\n<td>Calls the delegate on the UI thread and waits for it to complete.<\/td>\n<\/tr>\n<tr>\n<td><strong>Post<\/strong><\/td>\n<td><code>Control.InvokeAsync<\/code><\/td>\n<td>No<\/td>\n<td>Queues the delegate on the UI thread and returns immediately.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>Why This Matters<\/h3>\n<p>By posting delegates with <code>InvokeAsync<\/code>, your code can now queue multiple\nupdates to controls, perform background operations, or await other async tasks\nwithout halting the main UI thread. This approach not only helps prevent the\ndreaded \u201cfrozen UI\u201d experience but also keeps the app responsive even when\nhandling numerous UI-bound tasks.<\/p>\n<p>In summary: while <code>Control.Invoke<\/code> waits for the UI thread to complete the\ndelegate (blocking), <code>InvokeAsync<\/code> hands off the task to the UI thread and\nreturns instantly (non-blocking). This difference makes <code>InvokeAsync<\/code> ideal for\nasync scenarios, allowing developers to build smoother, more responsive WinForms\napplications.<\/p>\n<p>Here\u2019s how each <code>InvokeAsync<\/code> overload works:<\/p>\n<pre><code class=\"language-csharp\">public async Task InvokeAsync(Action callback, CancellationToken cancellationToken = default)\r\npublic async Task&lt;T&gt; InvokeAsync&lt;T&gt;(Func&lt;T&gt; callback, CancellationToken cancellationToken = default)\r\npublic async Task InvokeAsync(Func&lt;CancellationToken, ValueTask&gt; callback, CancellationToken cancellationToken = default)\r\npublic async Task&lt;T&gt; InvokeAsync&lt;T&gt;(Func&lt;CancellationToken, ValueTask&lt;T&gt;&gt; callback, CancellationToken cancellationToken = default)<\/code><\/pre>\n<p>Each overload allows for different combinations of synchronous and asynchronous\nmethods with or without return values:<\/p>\n<p><code>InvokeAsync(Action callback, CancellationToken cancellationToken = default)<\/code> is\nfor synchronous operations with <em>no<\/em> return value. If you want to update a\ncontrol\u2019s property on the UI thread\u2014such as setting the <code>Text<\/code> property on a\n<code>Label<\/code>\u2014this overload allows you to do so without waiting for a return value. The\ncallback will be posted to the message queue and executed asynchronously,\nreturning a <code>Task<\/code> that you can await if needed.<\/p>\n<pre><code class=\"language-csharp\">await control.InvokeAsync(() =&gt; control.Text = \"Updated Text\");<\/code><\/pre>\n<p><code>InvokeAsync&lt;T&gt;(Func&lt;T&gt; callback, CancellationToken cancellationToken = default)<\/code> is for synchronous operations that <em>do<\/em> return a result of type <code>T<\/code>.\nUse it when you want to retrieve a value computed on the UI thread, like getting\nthe <code>SelectedItem<\/code> from a <code>ComboBox<\/code>. <code>InvokeAsync<\/code> posts the callback to the UI\nthread and returns a <code>Task&lt;T&gt;<\/code>, allowing you to await the result.<\/p>\n<pre><code class=\"language-csharp\">int itemCount = await control.InvokeAsync(() =&gt; comboBox.Items.Count);<\/code><\/pre>\n<p><code>InvokeAsync(Func&lt;CancellationToken, ValueTask&gt; callback, CancellationToken cancellationToken = default):<\/code> This overload is for <em>asynchronous<\/em> operations\nthat <em>don\u2019t<\/em> return a result. It\u2019s ideal for a longer-running async operation\nthat updates the UI, such as waiting for data to load before updating a control.\nThe callback receives a <em><code>CancellationToken<\/code><\/em> to support cancellation and need to\nreturn a <em><code>ValueTask<\/code><\/em>, which <code>InvokeAsync<\/code> will await (internally) for\ncompletion, keeping the UI responsive while the operation runs asynchronously.\nSo, there are two &#8220;awaits happening&#8221;: <code>InvokeAsync<\/code> is awaited (or rather can be\nawaited), and internally the ValueTask that you passed is also awaited.<\/p>\n<pre><code class=\"language-csharp\">await control.InvokeAsync(async (ct) =&gt;\r\n{\r\n    await Task.Delay(1000, ct);  \/\/ Simulating a delay\r\n    control.Text = \"Data Loaded\";\r\n});<\/code><\/pre>\n<p><code>InvokeAsync&lt;T&gt;(Func&lt;CancellationToken, ValueTask&lt;T&gt;&gt; callback, CancellationToken cancellationToken = default)<\/code> is then finally the overload\nversion for <em>asynchronous<\/em> operations that <em>do<\/em> return a result of type <code>T<\/code>. Use\nit when an async operation must complete on the UI thread and return a value,\nsuch as querying a control\u2019s state after a delay or fetching data to update the\nUI. The callback receives a <code>CancellationToken<\/code> and returns a <code>ValueTask&lt;T&gt;<\/code>,\nwhich <code>InvokeAsync<\/code> will await to provide the result.<\/p>\n<pre><code class=\"language-csharp\">var itemCount = await control.InvokeAsync(async (ct) =&gt;\r\n{\r\n    await Task.Delay(500, ct);  \/\/ Simulating data fetching delay\r\n    return comboBox.Items.Count;\r\n});<\/code><\/pre>\n<h3>Quick decision lookup: Choosing the Right Overload<\/h3>\n<ul>\n<li>For no return value with synchronous operations, use <code>Action<\/code>.<\/li>\n<li>For return values with synchronous operations, use <code>Func&lt;T&gt;<\/code>.<\/li>\n<li>For async operations without a result, use <code>Func&lt;CancellationToken, ValueTask&gt;<\/code>.<\/li>\n<li>For async operations with a result, use <code>Func&lt;CancellationToken, ValueTask&lt;T&gt;&gt;<\/code>.<\/li>\n<\/ul>\n<p>Using the correct overload helps you handle UI tasks smoothly in async WinForms applications, avoiding main-thread bottlenecks and enhancing app responsiveness.<\/p>\n<p>Here\u2019s a quick example:<\/p>\n<pre><code class=\"language-csharp\">var control = new Control();\r\n\r\n\/\/ Sync action\r\nawait control.InvokeAsync(() =&gt; control.Text = \"Hello, async world!\");\r\n\r\n\/\/ Async function with return value\r\nvar result = await control.InvokeAsync(async (ct) =&gt;\r\n{\r\n    control.Text = \"Loading...\";\r\n    await Task.Delay(1000, ct);\r\n    control.Text = \"Done!\";\r\n    return 42;\r\n});<\/code><\/pre>\n<h3>Mixing-up asynchronous and synchronous overloads happen &#8211; or do they?<\/h3>\n<p>With so many overload options, it\u2019s possible to mistakenly pass an async method\nto a synchronous overload, which can lead to unintended &#8220;fire-and-forget&#8221;\nbehavior. To prevent this, WinForms introduces for .NET 9 a WinForms-specific\nanalyzer that detects when an asynchronous method (e.g., one returning <code>Task<\/code>) is\npassed to a synchronous overload of <code>InvokeAsync<\/code> without a <code>CancellationToken<\/code>.\nThe analyzer will trigger a warning, helping you identify and correct potential\nissues before they cause runtime problems.<\/p>\n<p>For example, passing an async method without <code>CancellationToken<\/code> support might\ngenerate a warning like:<\/p>\n<pre><code class=\"language-text\">warning WFO2001: Task is being passed to InvokeAsync without a cancellation token.<\/code><\/pre>\n<p>This Analyzer ensures that async operations are handled correctly, maintaining\nreliable, responsive behavior across your WinForms applications.<\/p>\n<h2>Experimental APIs<\/h2>\n<p>In addition to <code>InvokeAsync<\/code>, WinForms introduces experimental async options for\n.NET 9 for showing forms and dialogs. While still in experimental stages, these\nAPIs provide developers with greater flexibility for asynchronous UI\ninteractions, such as document management and form lifecycle control.<\/p>\n<p><code>Form.ShowAsync<\/code> and <code>Form.ShowDialogAsync<\/code> are new methods that allow forms to\nbe shown asynchronously. They simplify the handling of multiple form instances\nand are especially useful in cases where you might need several instances of the\nsame form type, such as when displaying different documents in separate windows.<\/p>\n<p>Here&#8217;s a basic example of how to use <code>ShowAsync<\/code>:<\/p>\n<pre><code class=\"language-csharp\">var myForm = new MyForm();\r\nawait myForm.ShowAsync();<\/code><\/pre>\n<p>And for modal dialogs, you can use <code>ShowDialogAsync<\/code>:<\/p>\n<pre><code class=\"language-csharp\">var result = await myForm.ShowDialogAsync();\r\nif (result == DialogResult.OK)\r\n{\r\n    \/\/ Perform actions based on dialog result\r\n}<\/code><\/pre>\n<p>These methods streamline the management of asynchronous form displays and help\nyou avoid blocking the UI thread while waiting for user interactions.<\/p>\n<h3>TaskDialog.ShowDialogAsync<\/h3>\n<p><code>TaskDialog.ShowDialogAsync<\/code> is another experimental API in .NET 9, aimed at\nimproving the flexibility of dialog interactions. It provides a way to show task\ndialogs asynchronously, perfect for use cases where lengthy operations or\nmultiple steps are involved.<\/p>\n<p>Here\u2019s how to display a <code>TaskDialog<\/code> asynchronously:<\/p>\n<pre><code class=\"language-csharp\">var taskDialogPage = new TaskDialogPage\r\n{\r\n    Heading = \"Processing...\",\r\n    Text = \"Please wait while we complete the task.\"\r\n};\r\n\r\nvar buttonClicked = await TaskDialog.ShowDialogAsync(taskDialogPage);<\/code><\/pre>\n<p>This API allows developers to asynchronously display dialogs, freeing the UI\nthread and providing a smoother user experience.<\/p>\n<h2>Practical Applications of Async APIs<\/h2>\n<p>These async APIs unlock new capabilities for WinForms, particularly in\nmulti-form applications, MVVM design patterns, and dependency injection\nscenarios. By leveraging async operations for forms and dialogs, you can:<\/p>\n<ul>\n<li><strong>Simplify form lifecycle management<\/strong> in async scenarios, especially when\nhandling multiple instances of the same form.<\/li>\n<li><strong>Support MVVM and DI workflows<\/strong>, where async form handling is beneficial in\nViewModel-driven architectures.<\/li>\n<li><strong>Avoid UI-thread blocking<\/strong>, enabling a more responsive interface even during\nintensive operations.<\/li>\n<\/ul>\n<p>If you curious about how <code>Invoke.Async<\/code> can revolutionize AI-driven\nmodernization of WinForms apps then check out <a href=\"https:\/\/youtu.be\/EBpJ99VriJk\">this .NET Conf 2024\ntalk<\/a> to see these features\ncome alive in real-world scenarios!<\/p>\n<p>&nbsp;<\/p>\n<p><iframe width=\"800\" height=\"450\" src=\"https:\/\/www.youtube.com\/embed\/EBpJ99VriJk\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<p>And that\u2019s not all\u2014don\u2019t miss our deep dive into everything new in .NET 9 for\nWinForms <a href=\"https:\/\/youtu.be\/1ZjCGdmQl_g?si=43PRkdjm41Y4XEwp\">in another exciting\ntalk<\/a>. Dive in and get\ninspired!<\/p>\n<h3>How to Kick Off Something Async from Something Sync<\/h3>\n<p>In UI scenarios, it\u2019s common to trigger async operations from synchronous\ncontexts. Of course, we all know it&#8217;s best practice to avoid <code>async void<\/code>\nmethods.<\/p>\n<p>Why is this the case? When you use <code>async void<\/code>, the caller has no way to await\nor observe the completion of the method. This can lead to unhandled exceptions\nor unexpected behavior. <code>async void<\/code> methods are essentially fire-and-forget,\nand they operate outside the standard error-handling mechanisms provided by\n<code>Task<\/code>. This makes debugging and maintenance more challenging in most scenarios.<\/p>\n<p>But! There is an exception, and that is event handlers or methods with &#8220;event\nhandler characteristics.&#8221; Event handlers cannot return <code>Task<\/code> or <code>Task&lt;T&gt;<\/code>, so\n<code>async void<\/code> allows them to trigger async actions without blocking the UI\nthread. However, because <code>async void<\/code> methods aren\u2019t awaitable, exceptions are\ndifficult to catch. To address this, you can use error-handling constructs like\n<code>try-catch<\/code> around the awaited operations inside the event handler. This ensures\nthat exceptions are properly handled even in these unique cases.<\/p>\n<p>For example:<\/p>\n<pre><code class=\"language-csharp\">private async void Button_Click(object sender, EventArgs e)\r\n{\r\n    try\r\n    {\r\n        await PerformLongRunningOperationAsync();\r\n    }\r\n    catch (Exception ex)\r\n    {\r\n        MessageBox.Show($\"An error occurred: {ex.Message}\", \"Error\", MessageBoxButtons.OK, MessageBoxIcon.Error);\r\n    }\r\n}<\/code><\/pre>\n<p>Here, the <code>async void<\/code> is unavoidable due to the event handler signature, but by\nwrapping the awaited code in a try-catch, we can safely handle any exceptions\nthat might occur during the async operation.<\/p>\n<p>The following example uses a 7-Segment control named <code>SevenSegmentTimer<\/code> to\ndisplay a timer in the typical 7-segment-style with the resolution of a 10th of\na second. It has a few methods for updating and animating the content:<\/p>\n<pre><code class=\"language-csharp\">public partial class TimerForm : Form\r\n{\r\n    private SevenSegmentTimer _sevenSegmentTimer;\r\n    private readonly CancellationTokenSource _formCloseCancellation = new();\r\n\r\n    public FrmMain()\r\n    {\r\n        InitializeComponent();\r\n        SetupTimerDisplay();\r\n    }\r\n\r\n    [MemberNotNull(nameof(_sevenSegmentTimer))]\r\n    private void SetupTimerDisplay()\r\n    {\r\n        _sevenSegmentTimer = new SevenSegmentTimer\r\n        {\r\n            Dock = DockStyle.Fill\r\n        };\r\n\r\n        Controls.Add(_sevenSegmentTimer);\r\n    }\r\n\r\n    override async protected void OnLoad(EventArgs e)\r\n    {\r\n        base.OnLoad(e);\r\n        await RunDisplayLoopAsyncV1();\r\n    }\r\n\r\n    private async Task RunDisplayLoopAsyncV1()\r\n    {\r\n        \/\/ When we update the time, the method will also wait 75 ms asynchronously.\r\n        _sevenSegmentTimer.UpdateDelay = 75;\r\n\r\n        while (true)\r\n        {\r\n            \/\/ We update and then wait for the delay.\r\n            \/\/ In the meantime, the Windows message loop can process other messages,\r\n            \/\/ so the app remains responsive.\r\n            await _sevenSegmentTimer.UpdateTimeAndDelayAsync(\r\n                time: TimeOnly.FromDateTime(DateTime.Now));\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<p>When we run this, we see this timer in the Form on the screen:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2024\/12\/StopWatchSimple.gif\" alt=\"WinForms App running a 7-segment stop watch in dark mode with green digits\" \/><\/p>\n<p>The async method <code>UpdateTimeAndDelayAsync<\/code> does exactly what it says: It updates\nthe time displayed in the control, and then waits the amount of time, which\nwe&#8217;ve set with the <code>UpdateDelay<\/code> property the line before.<\/p>\n<p>As you can see, this async method <code>RunDisplayLoopAsyncV1<\/code> is kicked-off in the\nForm&#8217;s <code>OnLoad<\/code>. And that&#8217;s the typical approach, how we initiate something\nasync from a synchronous void method.<\/p>\n<p>For the typical WinForms dev this may look a bit weird on first glance. After\nall, we&#8217;re calling another method from <code>OnLoad<\/code>, and that method never returns\nbecause it&#8217;s ending up in an endless loop. So, does <code>OnLoad<\/code> in this case ever\nfinish? Aren&#8217;t we blocking the app here?<\/p>\n<p>This is where async programming shines. Even though RunDisplayLoopAsyncV1\ncontains an infinite loop, it&#8217;s structured asynchronously. When the await\nkeyword is encountered inside the loop (e.g., <code>await _sevenSegmentTimer.UpdateTimeAndDelayAsync()<\/code>), the method yields control back\nto the caller until the awaited task completes.<\/p>\n<p>In the context of a WinForms app, this means the Windows message loop remains\nfree to process events like repainting the UI, handling button clicks, or\nresponding to keyboard input. The app stays responsive because <code>await<\/code> pauses\nthe execution of <code>RunDisplayLoopAsyncV1<\/code> without blocking the UI thread.<\/p>\n<p>When <code>OnLoad<\/code> is marked <code>async<\/code>, it completes as soon as it encounters its first\n<code>await<\/code> within <code>RunDisplayLoopAsyncV1<\/code>. After the awaited task completes, the\nruntime resumes execution of <code>RunDisplayLoopAsyncV1<\/code> from where it left off.\nThis happens without blocking the UI thread, effectively allowing <code>OnLoad<\/code> to\n<code>return<\/code> immediately, even though the asynchronous operation continues in the\nbackground.<\/p>\n<p>In the background? You can think of this as splitting the method into parts,\nlike an imaginary <code>WaitAsync-Initiator<\/code>, which gets called after the first\n<code>await<\/code> is resolved. Which then kicks-off the <code>WaitAsync-Waiter<\/code> which runs in\nthe background, until the wait period is over. Which then again triggers the\n<code>WaitAsync-Callback<\/code> which effectively asks the message loop to reentry the call\nand then complete everything which follows that async call.<\/p>\n<p>So, the actual code path looks then something like this:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2024\/12\/AsyncFromSyncStateDiagram.svg\" alt=\"State diagram showing the asynchronous flow of OnLoad and RunDisplayLoopAsyncV1 in a WinForms app, keeping the UI responsive.\" \/><\/p>\n<p>And the best way to internalize this is to compare it to 2 mouse-click events,\nwhich have been processed in succession, where the first mouse-click kicks off\n<code>RunDisplayLoopAsyncV1<\/code>, and the second mouse-click corresponds to the\n<code>WaitAsync<\/code> call-back into &#8220;Part 3&#8221; of that method, when the delay is just ready\nwaiting.<\/p>\n<p>This process then repeats for each subsequent <code>await<\/code> in an async method. And\nthis is why the app doesn\u2019t freeze despite the infinite loop. In fact,\ntechnically, OnLoad actually finishes normally, but the part(s) after each await\nare called back by the message loop later in time.<\/p>\n<p>Now, we&#8217;re still pretty much using the UI Thread exclusively here. (Technically\nspeaking, the call-backs for a short moment run on a thread-pool thread, but\nlet&#8217;s ignore that for now.) Yes, we&#8217;re async, but nothing so far is really\nhappening in parallel. Up to now, this is more like a clever ochestrated relay\nrace, where the baton is so seemlessly passed to the next respective runner,\nthat there simply are no hangs or blocks.<\/p>\n<p>But an async method can be called from a different thread at any time. And if we\ndid this currently in our sample like this&#8230;<\/p>\n<pre><code class=\"language-c#\">    private async Task RunDisplayLoopAsyncV2()\r\n    {\r\n        \/\/ When we update the time, the method will also wait 75 ms asynchronously.\r\n        _sevenSegmentTimer.UpdateDelay = 75;\r\n\r\n        \/\/ Let's kick-off a dedicated task for the loop.\r\n        await Task.Run(ActualDisplayLoopAsync);\r\n\r\n        \/\/ Local function, which represents the actual loop.\r\n        async Task ActualDisplayLoopAsync()\r\n        {\r\n            while (true)\r\n            {\r\n                \/\/ We update and then wait for the delay.\r\n                \/\/ In the meantime, the Windows message loop can process other messages,\r\n                \/\/ so the app remains responsive.\r\n                await _sevenSegmentTimer.UpdateTimeAndDelayAsync(\r\n                    time: TimeOnly.FromDateTime(DateTime.Now));\r\n            }\r\n        }\r\n    }<\/code><\/pre>\n<p>then&#8230;<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2024\/12\/CrossThreadException2.png\" alt=\"Screenshot of a Cross-Thread-Exception in the demo-app's context\" \/><\/p>\n<h3>The trickiness of InvokeAsync&#8217;s overload resolution<\/h3>\n<p>So, as we learned earlier, this is an easy one to resolve, right? We&#8217;re just\nusing <code>InvokeAsync<\/code> to call our local function <code>ActualDisplayLoopAsync<\/code>, and\nthen we&#8217;re good. So, let&#8217;s do that. Let&#8217;s get the <code>Task<\/code> that is returned by\nInvokeAsync and pass that to <code>Task.Run<\/code>. Easy-peasy.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2024\/12\/ErrorAndWarning.png\" alt=\"Screenshot Errors and Warnings pointing to overload resolution issues\" \/><\/p>\n<p>Well &#8211; that doesn&#8217;t look so good. We got 2 issues. First, as mentioned before,\nwe&#8217;re trying to invoke a method returning a <code>Task<\/code> without a cancellation token.\n<code>InvokeAsync<\/code> is warning us that we are setting up a fire-and-forget in this\ncase, which cannot be internally awaited. And the second issue is not only a\nwarning, it is an error. <code>InvokeAsync<\/code> is returning a <code>Task<\/code>, and we of course\ncannot pass that to <code>Task.Run<\/code>. We can only pass an <code>Action<\/code> or a <code>Func<\/code>\n<em>returning<\/em> a <code>Task<\/code>, but surely not a <code>Task<\/code> itself. But, what we can do, is\njust converting this line into another local function, so from this&#8230;<\/p>\n<pre><code class=\"language-c#\">\/\/ Doesn't work. InvokeAsync wants a cancellation token, and we can't pass Task.Run a task.\r\nvar invokeTask = this.InvokeAsync(ActualDisplayLoopAsync);\r\n\r\n\/\/ Let's kick-off a dedicated task for the loop.\r\nawait Task.Run(invokeTask);\r\n\r\n\/\/ Local function, which represents the actual loop.\r\nasync Task ActualDisplayLoopAsync(CancellationToken cancellation)<\/code><\/pre>\n<p>to this:<\/p>\n<pre><code class=\"language-c#\">\/\/ This is a local function now, calling the actual loop on the UI Thread.\r\nTask InvokeTask() =&gt; this.InvokeAsync(ActualDisplayLoopAsync, CancellationToken.None);\r\n\r\nawait Task.Run(InvokeTask);\r\n\r\nasync ValueTask ActualDisplayLoopAsync(CancellationToken cancellation=default)\r\n...<\/code><\/pre>\n<p>And that works like a charm now!<\/p>\n<h2>Parallelizing for Performance or targeted code flow<\/h2>\n<p>Our 7-segment control has another neat trick up its sleeve: a fading animation\nfor the separator columns. We can use this feature as follows:<\/p>\n<pre><code class=\"language-c#\">    private async Task RunDisplayLoopAsyncV4()\r\n    {\r\n        while (true)\r\n        {\r\n            \/\/ We also have methods to fade the separators in and out!\r\n            \/\/ Note: There is no need to invoke these methods on the UI thread,\r\n            \/\/ because we can safely set the color for a label from any thread.\r\n            await _sevenSegmentTimer.FadeSeparatorsInAsync().ConfigureAwait(false);\r\n            await _sevenSegmentTimer.FadeSeparatorsOutAsync().ConfigureAwait(false);\r\n        }\r\n    }<\/code><\/pre>\n<p>When we run this, it looks like this:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2024\/12\/StopWatchFadingSeps.gif\" alt=\"WinForms App running showing the 7-segment control with separator animation\" \/><\/p>\n<p>However, there\u2019s a challenge: How can we set up our code flow so that the\nrunning clock and the fading separators are invoked in parallel, all within a\ncontinuous loop?<\/p>\n<p>To achieve this, we can leverage Task-based parallelism. The idea is to:<\/p>\n<ol>\n<li><strong>Run both the clock update and the separator fading simultaneously:<\/strong> We execute\nboth tasks asynchronously and wait for them to complete.<\/li>\n<li><strong>Handle differing task durations gracefully:<\/strong> Since the clock update and fading\nanimations might take different amounts of time, we use <code>Task.WhenAny<\/code> to\nensure the faster task doesn\u2019t delay the slower one.<\/li>\n<li><strong>Reset completed tasks:<\/strong> Once a task completes, we reset it to null so the next\niteration can start it anew.<\/li>\n<\/ol>\n<p>And the result is this:<\/p>\n<pre><code class=\"language-c#\">    private async Task RunDisplayLoopAsyncV6()\r\n    {\r\n        Task? uiUpdateTask = null;\r\n        Task? separatorFadingTask = null;\r\n\r\n        while (true)\r\n        {\r\n            async Task FadeInFadeOutAsync(CancellationToken cancellation)\r\n            {\r\n                await _sevenSegmentTimer.FadeSeparatorsInAsync(cancellation).ConfigureAwait(false);\r\n                await _sevenSegmentTimer.FadeSeparatorsOutAsync(cancellation).ConfigureAwait(false);\r\n            }\r\n\r\n            uiUpdateTask ??= _sevenSegmentTimer.UpdateTimeAndDelayAsync(\r\n                time: TimeOnly.FromDateTime(DateTime.Now),\r\n                cancellation: _formCloseCancellation.Token);\r\n\r\n            separatorFadingTask ??= FadeInFadeOutAsync(_formCloseCancellation.Token);\r\n            Task completedOrCancelledTask = await Task.WhenAny(separatorFadingTask, uiUpdateTask);\r\n\r\n            if (completedOrCancelledTask.IsCanceled)\r\n            {\r\n                break;\r\n            }\r\n\r\n            if (completedOrCancelledTask == uiUpdateTask)\r\n            {\r\n                uiUpdateTask = null;\r\n            }\r\n            else\r\n            {\r\n                separatorFadingTask = null;\r\n            }\r\n        }\r\n    }\r\n\r\n    protected override void OnFormClosing(FormClosingEventArgs e)\r\n    {\r\n        base.OnFormClosing(e);\r\n        _formCloseCancellation.Cancel();\r\n    }<\/code><\/pre>\n<p>And this. And you can see in this animated GIF, that the UI really stays\nresponsive all the time, because the window can be smoothly dragged around with\nthe mouse.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2024\/12\/StopWatchFinalVersion.gif\" alt=\"Final animated version of the 7-segment timer app\" \/><\/p>\n<h2>Summary<\/h2>\n<p>With these new async APIs, .NET 9 brings advanced capabilities to WinForms,\nmaking it easier to work with asynchronous UI operations. While some APIs, like\n<code>Control.InvokeAsync<\/code>, are ready for production, experimental APIs for Form and\nDialog management open up exciting possibilities for responsive UI development.<\/p>\n<p>You can find the sample code of this blog post in our\n<a href=\"https:\/\/github.com\/microsoft\/winforms-designer-extensibility\">Extensibility-Repo<\/a>\nin the <a href=\"https:\/\/github.com\/microsoft\/winforms-designer-extensibility\/tree\/main\/Samples\/NET%209\/Async%20in%20NET%209\">respective Samples\nsubfolder<\/a>.<\/p>\n<p>Explore the potential of async programming in WinForms with .NET 9, and be sure\nto test out the experimental features in non-critical projects. As always, your\nfeedback is invaluable, and we look forward to hearing how these new async\ncapabilities enhance your development process!<\/p>\n<p>And, as always: Happy Coding!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>With .NET 9, we\u2019re bringing a suite of new async APIs to WinForms, aimed at making UI updates, dialog interactions, and control management more efficient.<\/p>\n","protected":false},"author":9483,"featured_media":54847,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,7163],"tags":[7797],"class_list":["post-54846","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-winforms","tag-dotnet-9"],"acf":[],"blog_post_summary":"<p>With .NET 9, we\u2019re bringing a suite of new async APIs to WinForms, aimed at making UI updates, dialog interactions, and control management more efficient.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/54846","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/9483"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=54846"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/54846\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/54847"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=54846"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=54846"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=54846"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}