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