{"id":20628683,"url":"https://github.com/thoemmi/crossthreadingtests","last_synced_at":"2025-10-20T06:37:02.928Z","repository":{"id":138005001,"uuid":"168039583","full_name":"thoemmi/CrossThreadingTests","owner":"thoemmi","description":"Async/await in Desktop Applications","archived":false,"fork":false,"pushed_at":"2019-01-28T21:39:14.000Z","size":54,"stargazers_count":6,"open_issues_count":0,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-15T16:26:03.312Z","etag":null,"topics":["async","desktop-application","dotnet","multithreading","winforms","wpf"],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thoemmi.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-01-28T21:22:08.000Z","updated_at":"2023-03-26T20:43:42.000Z","dependencies_parsed_at":null,"dependency_job_id":"89b05f6c-3c57-454f-beff-5908b1d1fb1a","html_url":"https://github.com/thoemmi/CrossThreadingTests","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/thoemmi/CrossThreadingTests","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoemmi%2FCrossThreadingTests","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoemmi%2FCrossThreadingTests/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoemmi%2FCrossThreadingTests/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoemmi%2FCrossThreadingTests/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thoemmi","download_url":"https://codeload.github.com/thoemmi/CrossThreadingTests/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoemmi%2FCrossThreadingTests/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262996408,"owners_count":23396903,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","desktop-application","dotnet","multithreading","winforms","wpf"],"created_at":"2024-11-16T13:22:13.471Z","updated_at":"2025-10-20T06:36:57.890Z","avatar_url":"https://github.com/thoemmi.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Async/await in Desktop Applications\n\nThis repository contains a sample WinForms application to demonstrate\nasync/await \"challenges\" in UI programming.\n\n![Demo App](assets/winforms-app.png)\n\nThe form contains three buttons which are intended to download the\nthe content from http://microsoft.com asynchronously and show it\nin a TextBox.\n\n## Cross-Thread issue :(\n\n```csharp\nprivate async void button1_Click(object sender, EventArgs e)\n{\n    // await a completed task =\u003e will continue synchronously\n    await SomeFastAsyncOperation().ConfigureAwait(false);\n\n    // await a slow task =\u003e will continue in another thread\n    var t = await SomeSlowAsyncOperation().ConfigureAwait(false);\n\n    // write text to text box\n    textBox1.Text = t;\n}\n```\n\nWhen clicking the first button, an `InvalidOperationException` will\nbe thrown with the message\n\n\u003e Cross-thread operation not valid: Control 'textBox1' accessed from a\n\u003e thread other than the thread it was created on.\n\nThat's because in Win32 UIs you must access controls from the same thread\nthat created them. Because the after awaiting `SomeSlowAsyncOperation`\nthe method continues not in the main thread but a nackground thread,\naccessing `textBox1` is forbidden.\n\nWhen you debug `button1_Click`, pay attention to the *Threads* tool window.\n\n![Debugger](assets/debugger.png \"Debugger Thread Toolwindow\")\n\n1. Entering the method, you'll be on thread #1, the main thread.\n2. After calling `SomeFastAsyncOperation`, the code continues on thread #1.\n   That's because the method returns an already completed task, so the code\n   can continue synchronously.\n3. In contrast, `SomeSlowAsyncOperation` returns a not-completed task, \n   therefore the succeeding code will continue not another one. (The UI thread\n   will be released here and continue pumping the Win32 message queue)\n4. The property `textBox1.Text` will be set in said background thread and\n   fail, because Win32 controls mist be accessed from the same thread that\n   created them.\n\n\n## Blocking :(\n\n```csharp\nprivate async void button2_Click(object sender, EventArgs e)\n{\n    // await a completed task =\u003e will continue synchronously\n    await SomeFastAsyncOperation().ConfigureAwait(false);\n\n    // await a slow task =\u003e will continue in another thread\n    var t = SomeSlowAsyncOperation().ConfigureAwait(false).GetAwaiter().GetResult();\n\n    // write text to text box\n    textBox1.Text = t;\n}\n```\n\nClicking the second button will freeze the application. The\n`GetAwaiter().GetResult()` invocation will try to re-enter the\nthe main thread, which is waiting for the task, so we'll run into a dead-lock.\n\n## Run smoothly :)\n\n```csharp\nprivate async void button3_Click(object sender, EventArgs e)\n{\n    // force switch to threadpool thread\n    await TaskScheduler.Default;\n\n    // await a completed task =\u003e will continue synchronously\n    await SomeFastAsyncOperation().ConfigureAwait(false);\n\n    // await a slow task =\u003e will continue in another thread\n    var t = await SomeSlowAsyncOperation().ConfigureAwait(false);\n\n    // switch to main thread\n    await _joinableTaskFactory.SwitchToMainThreadAsync();\n\n    // write text to text box\n    textBox1.Text = t;\n}\n```\n\nUsing the `JoinableTaskFactory` from [_Microsoft.VisualStudio.Threading_](https://github.com/Microsoft/vs-threading)\n(NuGet package [here](https://nuget.org/packages/Microsoft.VisualStudio.Threading)),\nwe are able to \"switch\" back to the UI thread.\n\nAs the namespace implies, _Microsoft.VisualStudio.Threading_ originates from\nthe Visual Studio team. I stumbled over this library while reading the documentation\nfor Visual Studio extensibility. Visual Studio is quite a complex application, and\nthere are myriads of extensions available. To improve the start-up time, Microsoft\nstrongly recommends to make use of asynchronous programming (see\n[_How to: Manage multiple threads in managed code_](https://docs.microsoft.com/en-us/visualstudio/extensibility/managing-multiple-threads-in-managed-code?view=vs-2017)\nand [_How to: Use AsyncPackage to load VSPackages in the background_](https://docs.microsoft.com/en-us/visualstudio/extensibility/how-to-use-asyncpackage-to-load-vspackages-in-the-background?view=vs-2017))\n\nI won't go into details of how async/await works. Basically, the compiler generates\na state machine, which Dixin explains pretty good in his blog serie\n[_Understanding C# async / await (1) Compilation_](https://weblogs.asp.net/dixin/understanding-c-sharp-async-await-1-compilation) \n([Part 2](https://weblogs.asp.net/dixin/understanding-c-sharp-async-await-2-awaitable-awaiter-pattern), \n[Part 3](https://weblogs.asp.net/dixin/understanding-c-sharp-async-await-3-runtime-context)).\n\nIn our case, two calls are interesting:\n\n1. `await TaskScheduler.Default;` will continue the succeeding code in a threadpool thread\u003cbr\u003e\n   \u003csmall\u003e(actually, the library provides an extension method `GetAwaiter(this TaskScheduler this)`.\n   This works because the compiler uses a naming convention instead of requiring an interface implemenntation)\u003c/small\u003e\n2. `await _joinableTaskFactory.SwitchToMainThreadAsync();` will continue the succeeding code in the\n   main thread \u003cbr\u003e\u003csmall\u003e(actually, in the thread with instantiated `_joinableTaskFactory`).\u003c/small\u003e\n\nAs you could see, _Microsoft.VisualStudio.Threading_ makes asynchronous programming\nin desktop applications, both WinForms and WPF, much simpler.\n\nBTW, I've learned a lot reading the [code of that library](https://github.com/Microsoft/vs-threading). It provides\nmuch more async helpers like `AsyncEventHandlers`.\n\nHere are some more links if you want to learn more about async programming:\n\n* [_The 3 VS Threading Rules_](https://www.slideshare.net/aarnott/the-3-vs-threading-rules) by Andrew Arnott\n\n* [_Don't Block on Async Code_](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html) by Stephen Cleary\n\n* [_Concurrency in C# Cookbook: Asynchronous, Parallel, and Multithreaded Programming_](https://lesen.amazon.de/kp/embed?asin=B00KCY2CB4\u0026preview=newtab\u0026linkCode=kpe\u0026ref_=cm_sw_r_kb_dp_WWQtCbZETE973) by Stephen Cleary\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthoemmi%2Fcrossthreadingtests","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthoemmi%2Fcrossthreadingtests","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthoemmi%2Fcrossthreadingtests/lists"}