diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index bc0b45c86..000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,25 +0,0 @@ -# Please see the documentation for all configuration options: -# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file - -version: 2 -updates: -- package-ecosystem: nuget - directory: / - schedule: - interval: weekly - groups: - nerdbank-gitversioning: - patterns: - - nbgv - - nerdbank.gitversioning - xunit: - patterns: - - 'xunit*' -- package-ecosystem: dotnet-sdk - directory: / - schedule: - interval: monthly - ignore: - - dependency-name: Microsoft.CodeAnalysis* # We intentionally target older VS versions. - - dependency-name: Microsoft.Bcl.AsyncInterfaces # We want to match the minimum target .NET runtime - - dependency-name: System.Collections.Immutable # We want to match the minimum target .NET runtime diff --git a/.github/renovate.json b/.github/renovate.json index 666067623..b2611c4de 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -21,9 +21,36 @@ "matchDepNames": ["dotnet-sdk", "mcr.microsoft.com/dotnet/sdk"], "groupName": "Dockerfile and global.json updates" }, + { + "matchPackageNames": [ + "System.Collections.Immutable", + "System.Composition*", + "System.Diagnostics.DiagnosticSource", + "System.IO.Pipelines", + "System.Reflection.Metadata", + "System.Text.Json", + "System.Threading.Tasks.Dataflow", + "Microsoft.Bcl.AsyncInterfaces" + ], + "allowedVersions": "<9.0", + "groupName": "Included in .NET runtime" + }, { "matchPackageNames": ["Microsoft.VisualStudio.Internal.MicroBuild*"], "groupName": "microbuild" + }, + { + "matchPackageNames": ["Microsoft.VisualStudio.*"], + "groupName": "Visual Studio SDK" + }, + { + "matchPackageNames": ["Microsoft.VisualStudio.*"], + "matchUpdateTypes": ["patch"], + "enabled": false + }, + { + "matchJsonata": [ "sharedVariableName='CodeAnalysisVersion'" ], + "enabled": false } ] } diff --git a/.github/workflows/docs_validate.yml b/.github/workflows/docs_validate.yml index 4b3d4076b..2c298e01b 100644 --- a/.github/workflows/docs_validate.yml +++ b/.github/workflows/docs_validate.yml @@ -3,19 +3,23 @@ name: 📃 Docfx Validate on: pull_request: workflow_dispatch: + push: + branches: + - main + - microbuild jobs: build: name: 📚 Doc validation - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # avoid shallow clone so nbgv can do its work. - name: 🔗 Markup Link Checker (mlc) - uses: becheran/mlc@v0.19.0 + uses: becheran/mlc@v0.19.2 with: - args: --do-not-warn-for-redirect-to https://learn.microsoft.com*,https://dev.azure.com/*,https://app.codecov.io/*,https://msrc.microsoft.com/*,https://www.microsoft.com/en-us/msrc* -p docfx -i https://aka.ms/onboardsupport,https://aka.ms/spot,https://www.microsoft.com/msrc/cvd,https://www.microsoft.com/msrc,https://microsoft.sharepoint.com/* + args: --do-not-warn-for-redirect-to https://learn.microsoft.com*,https://dotnet.microsoft.com/*,https://dev.azure.com/*,https://app.codecov.io/*,https://badges.gitter.im/*,https://github.com/*,https://app.gitter.im/* -p docfx -i https://aka.ms/onboardsupport,https://aka.ms/spot,https://msrc.microsoft.com/*,https://www.microsoft.com/msrc*,https://microsoft.com/msrc*,https://microsoft.sharepoint.com/* - name: ⚙ Install prerequisites run: | ./init.ps1 -UpgradePrerequisites diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b72a21042..c3e106809 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ For significant changes we strongly recommend opening an issue to start a design * [.NET Core SDK](https://dotnet.microsoft.com/download/dotnet-core/2.2) with the version matching our [global.json](global.json) file. The version you install must be at least the version specified in the global.json file, and must be within the same hundreds version for the 3rd integer: x.y.Czz (x.y.C must match, and zz must be at least as high). The easiest way to get this is to run the `init` script at the root of the repo. Use the `-InstallLocality Machine` and approve admin elevation if you wish so the SDK is always discoverable from VS. See the `init` script usage doc for more details. -* Optional: [Visual Studio 2019](https://www.visualstudio.com/) +* Optional: [Visual Studio 2022](https://visualstudio.microsoft.com/) The only prerequisite for building, testing, and deploying from this repository is the [.NET SDK](https://get.dot.net/). diff --git a/Directory.Packages.props b/Directory.Packages.props index 7a407cdcc..56ac743c8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,9 +4,9 @@ true true - 2.0.171 + 2.0.181 3.11.0 - 4.4.0 + 4.4.0 1.1.2 3.1.525101 3.11.0-beta1.24527.2 @@ -43,6 +43,12 @@ + + + + + + diff --git a/README.md b/README.md index ba08fea4f..97da5667f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## Microsoft.VisualStudio.Threading -[![NuGet package](https://img.shields.io/nuget/v/Microsoft.VisualStudio.Threading.svg)](https://nuget.org/packages/Microsoft.VisualStudio.Threading) +[![NuGet package](https://img.shields.io/nuget/v/Microsoft.VisualStudio.Threading.svg)](https://www.nuget.org/packages/Microsoft.VisualStudio.Threading) Async synchronization primitives, async collections, TPL and dataflow extensions. The JoinableTaskFactory allows synchronously blocking the UI thread for async work. This package is applicable to any .NET application (not just Visual Studio). @@ -15,7 +15,7 @@ Async synchronization primitives, async collections, TPL and dataflow extensions ## Microsoft.VisualStudio.Threading.Analyzers -[![NuGet package](https://img.shields.io/nuget/v/Microsoft.VisualStudio.Threading.Analyzers.svg)](https://nuget.org/packages/Microsoft.VisualStudio.Threading.Analyzers) +[![NuGet package](https://img.shields.io/nuget/v/Microsoft.VisualStudio.Threading.Analyzers.svg)](https://www.nuget.org/packages/Microsoft.VisualStudio.Threading.Analyzers) Static code analyzer to detect common mistakes or potential issues regarding threading and async coding. diff --git a/doc/analyzers/VSTHRD010.md b/doc/analyzers/VSTHRD010.md index 022d76554..bfb01c11d 100644 --- a/doc/analyzers/VSTHRD010.md +++ b/doc/analyzers/VSTHRD010.md @@ -58,4 +58,4 @@ private async Task CallVSAsync() } ``` -Refer to [Asynchronous and multithreaded programming within VS using the JoinableTaskFactory](http://blogs.msdn.com/b/andrewarnottms/archive/2014/05/07/asynchronous-and-multithreaded-programming-within-vs-using-the-joinabletaskfactory/) for more info. +Refer to [Asynchronous and multithreaded programming within VS using the JoinableTaskFactory](https://devblogs.microsoft.com/premier-developer/asynchronous-and-multithreaded-programming-within-vs-using-the-joinabletaskfactory/) for more info. diff --git a/doc/threading_rules.md b/doc/threading_rules.md index 95a022fcc..f6de5c29a 100644 --- a/doc/threading_rules.md +++ b/doc/threading_rules.md @@ -13,11 +13,11 @@ extensions](cookbook_vs.md)). ## The Rules -The rules are listed below with minimal examples. For a more thorough explanation with more examples, check out [this slideshow](https://www.slideshare.net/aarnott/the-3-vs-threading-rules). +The rules are listed below with minimal examples. For a more thorough explanation with more examples, check out [this slideshow](https://www.slideshare.net/slideshow/the-3-vs-threading-rules/78280010). ### Rule #1. If a method has certain thread apartment requirements (STA or MTA) it must either: 1. Have an asynchronous signature, and asynchronously marshal to the appropriate - thread if it isn't originally invoked on a compatible thread. The recommended + thread if it isn't originally invoked on a compatible thread. The recommended means of switching to the main thread is: ```csharp @@ -25,7 +25,7 @@ The rules are listed below with minimal examples. For a more thorough explanatio ``` OR - + 2. Have a synchronous signature, and throw an exception when called on the wrong thread. This can be done in Visual Studio with `ThreadHelper.ThrowIfNotOnUIThread()` or `ThreadHelper.ThrowIfOnUIThread()`. @@ -33,7 +33,7 @@ The rules are listed below with minimal examples. For a more thorough explanatio In particular, no method is allowed to synchronously marshal work to another thread (blocking while that work is done) except by using the second rule (below). Synchronous blocks in general are to be avoided whenever possible. - + ### Rule #2. When an implementation of an already-shipped public API must call asynchronous code and block for its completion, it must do so by following this simple pattern: ```csharp @@ -42,7 +42,7 @@ joinableTaskFactoryInstance.Run(async delegate await SomeOperationAsync(...); }); ``` - + ### Rule #3. If ever awaiting work that was started earlier, that work must be *joined*. For example, one service kicks off some asynchronous work that may later become synchronously blocking: @@ -57,22 +57,22 @@ JoinableTask longRunningAsyncWork = joinableTaskFactoryInstance.RunAsync( then later that async work becomes blocking: -```csharp +```csharp longRunningAsyncWork.Join(); ``` -or perhaps +or perhaps -```csharp +```csharp await longRunningAsyncWork; ``` Note however that this extra step is not necessary when awaiting is done immediately after kicking off an asynchronous operation. - -In particular, no method should call `Task.Wait()` or `Task.Result` on + +In particular, no method should call `Task.Wait()` or `Task.Result` on an incomplete `Task`. - + ### Additional "honorable mention" rules: (Not JTF related) ### Rule #4. Never define `async void` methods. Make the methods return `Task` instead. @@ -81,11 +81,11 @@ an incomplete `Task`. - Exceptions can't be reported to telemetry by the caller. - It's impossible for your VS package to responsibly block in `Package.Close` till your `async` work is done when it was kicked off this way. - - Be cautious: `async delegate` or `async () =>` become `async void` - methods when passed to a method that accepts `Action` delegates. Only - pass `async` delegates to methods that accept `Func` or + - Be cautious: `async delegate` or `async () =>` become `async void` + methods when passed to a method that accepts `Action` delegates. Only + pass `async` delegates to methods that accept `Func` or `Func>` parameters. - + Frequently Asked Questions --------------- @@ -139,65 +139,65 @@ blocking thread to execute the continuations. There are several reasons for this: -1. The COM transition synchronously blocks the calling thread. If the - main thread isn't immediately pumping messages, the MTA thread will - block until it handles the message. If you're on a threadpool thread, - this ties up a precious resource and if your code may execute on - multiple threadpool threads at once, there is a very real possibility +1. The COM transition synchronously blocks the calling thread. If the + main thread isn't immediately pumping messages, the MTA thread will + block until it handles the message. If you're on a threadpool thread, + this ties up a precious resource and if your code may execute on + multiple threadpool threads at once, there is a very real possibility of [threadpool starvation](threadpool_starvation.md). -2. Deadlock: if the main thread is blocked waiting for the background - thread, and the main thread happens to be on top of some call stack - (like WPF measure-layout) that suppresses the message pump, the code +2. Deadlock: if the main thread is blocked waiting for the background + thread, and the main thread happens to be on top of some call stack + (like WPF measure-layout) that suppresses the message pump, the code that normally works will randomly deadlock. -3. When the main thread is pumping messages, it will execute your code, - regardless as to whether it is relevant to what the main thread may - already be doing. If the main thread is in the main message pump, - that's fine. But if the main thread is in a pumping wait (in - managed code this could be almost anywhere as this includes locks, - I/O, sync blocks, etc.) it could be a very bad time. We call these - bad times "reentrancy" and the problem comes when you have component - X running on the main thread in a pumping wait, the component Y - uses COM marshalling to re-enter the main thread, and then Y calls - (directly or indirectly) into component X. Component X is typically - written with the assumption that by being on the main thread, it's - isolated and single-threaded, and it usually isn't prepared to handle - reentrancy. As a result, data corruption and/or deadlocks can result. - Such has been the source of many deadlocks and crashes in VS for the +3. When the main thread is pumping messages, it will execute your code, + regardless as to whether it is relevant to what the main thread may + already be doing. If the main thread is in the main message pump, + that's fine. But if the main thread is in a pumping wait (in + managed code this could be almost anywhere as this includes locks, + I/O, sync blocks, etc.) it could be a very bad time. We call these + bad times "reentrancy" and the problem comes when you have component + X running on the main thread in a pumping wait, the component Y + uses COM marshalling to re-enter the main thread, and then Y calls + (directly or indirectly) into component X. Component X is typically + written with the assumption that by being on the main thread, it's + isolated and single-threaded, and it usually isn't prepared to handle + reentrancy. As a result, data corruption and/or deadlocks can result. + Such has been the source of many deadlocks and crashes in VS for the last few releases. -4. Any method from a VS service that returns a pointer is probably - inherently broken when called from a background thread. For example, - `ItemID`s returned from `IVsHierarchy` are very often raw pointers cast - to integers. These pointers are guaranteed to be valid for as long - as you're on the main thread (and no event was raised to invalidate - it). But when you call a `IVsHierarchy` method to get an `ItemID` back - from a background thread, you leave the STA thread immediately as - the call returns, meaning the pointer is unsafe to use. If you then - go and pass that pointer back into the project system, the pointer - could have been invalidated in the interim, and you'll end up causing - an access violation crash in VS. The only safe way to deal with - `ItemID`s (or any other pointer type) is while manually marshaled to - the UI thread so that you know they are still valid for as long as +4. Any method from a VS service that returns a pointer is probably + inherently broken when called from a background thread. For example, + `ItemID`s returned from `IVsHierarchy` are very often raw pointers cast + to integers. These pointers are guaranteed to be valid for as long + as you're on the main thread (and no event was raised to invalidate + it). But when you call a `IVsHierarchy` method to get an `ItemID` back + from a background thread, you leave the STA thread immediately as + the call returns, meaning the pointer is unsafe to use. If you then + go and pass that pointer back into the project system, the pointer + could have been invalidated in the interim, and you'll end up causing + an access violation crash in VS. The only safe way to deal with + `ItemID`s (or any other pointer type) is while manually marshaled to + the UI thread so that you know they are still valid for as long as you hold and use them. -5. If your method runs on a background thread and has a loop that - accesses a VS service, that can incur a lot of thread transitions - which can hurt performance. If you were explicit in your code about - the transition, you'd very likely move it to just before you enter +5. If your method runs on a background thread and has a loop that + accesses a VS service, that can incur a lot of thread transitions + which can hurt performance. If you were explicit in your code about + the transition, you'd very likely move it to just before you enter the loop, which would make your code more efficient from the start. -6. Some VS services don't have proxy stubs registered and thus will fail - to the type cast or on method invocation when your code executes on +6. Some VS services don't have proxy stubs registered and thus will fail + to the type cast or on method invocation when your code executes on a background thread. -7. Some VS services get rewritten from native to managed code, which - subtly changes them from single-threaded to free-threaded services. - Unless the managed code is written to be thread-safe (most is not) - this means that your managed code calling into a managed code VS - service on a background thread will not transition to the UI thread - first, and you are cruising for thread-safety bugs (data corruption, - crashes, hangs, etc). By switching to the main thread yourself first, - you won't be the poor soul who has crashes in their feature and has - to debug it for days until you finally figure out that you were causing - data corruption and a crash later on. Yes, you can blame the free - threaded managed code that should have protected itself, but that's - not very satisfying after days of investigation. And the owner of +7. Some VS services get rewritten from native to managed code, which + subtly changes them from single-threaded to free-threaded services. + Unless the managed code is written to be thread-safe (most is not) + this means that your managed code calling into a managed code VS + service on a background thread will not transition to the UI thread + first, and you are cruising for thread-safety bugs (data corruption, + crashes, hangs, etc). By switching to the main thread yourself first, + you won't be the poor soul who has crashes in their feature and has + to debug it for days until you finally figure out that you were causing + data corruption and a crash later on. Yes, you can blame the free + threaded managed code that should have protected itself, but that's + not very satisfying after days of investigation. And the owner of that code may refuse to fix their code and you'll have to fix yours anyway. ##### How do these rules protect me from re-entering random code on the main thread? @@ -243,7 +243,7 @@ the moment. The debugger and Windows teams are working to improve that situation. In the meantime, we have learned several techniques to figure out what is causing the hang, and we're working to enhance the framework to automatically detect, self-analyze and report hangs to you so you have -almost nothing to do but fix the code bug. +almost nothing to do but fix the code bug. In the meantime, the most useful technique for analyzing async hangs is to attach WinDBG to the process and dump out incomplete async methods' states. @@ -283,7 +283,7 @@ priority via the `JoinableTask` it may call your code within. ##### What message priority is used to switch to (or resume on) the main thread, and can this be changed? -`JoinableTaskFactory`’s default behavior is to switch to the main thread using +`JoinableTaskFactory`'s default behavior is to switch to the main thread using `SynchronizationContext.Post`, which typically posts a message to the main thread, which puts it below RPC and above user input in priority. @@ -303,8 +303,8 @@ your own constructor that chains in the base constructor, passing in the required parameters. You are then free to directly instantiate your derived type by passing in either a `JoinableTaskContext` or a `JoinableTaskCollection`. -For more information on this topic, see Andrew Arnott's blog post -[Asynchronous and multithreaded programming within VS using the +For more information on this topic, see Andrew Arnott's blog post +[Asynchronous and multithreaded programming within VS using the `JoinableTaskFactory`][JTFBlog]. [AsyncHangDebugging]: https://github.com/Microsoft/VSProjectSystem/blob/master/doc/scenario/analyze_hangs.md diff --git a/doc/threadpool_starvation.md b/doc/threadpool_starvation.md index 8162c5e84..73222d14a 100644 --- a/doc/threadpool_starvation.md +++ b/doc/threadpool_starvation.md @@ -73,7 +73,7 @@ There are multiple major causes of thread pool starvation. Each is briefly descr ### Blocking a thread pool thread while waiting for the UI thread -When a thread pool thread tries to access an STA COM object such as Visual Studio's IServiceProvider or a service previously obtained from this interface, the call to that COM object will require an RPC (Remote Procedure Call) transition which blocks the thread pool thread until the UI thread has time to respond to the request. Learn more about RPC calls from [this blog post](https://blogs.msdn.microsoft.com/andrewarnottms/2014/05/07/asynchronous-and-multithreaded-programming-within-vs-using-the-joinabletaskfactory/). +When a thread pool thread tries to access an STA COM object such as Visual Studio's IServiceProvider or a service previously obtained from this interface, the call to that COM object will require an RPC (Remote Procedure Call) transition which blocks the thread pool thread until the UI thread has time to respond to the request. Learn more about RPC calls from [this blog post](https://devblogs.microsoft.com/premier-developer/asynchronous-and-multithreaded-programming-within-vs-using-the-joinabletaskfactory/). The mitigation for this is to have the method that is executing on the thread pool asynchronously switch to the UI thread *before* calling into an STA COM object. This allows the thread pool thread to work on something else on the thread pool's queue while the UI thread is busy or servicing this request. After interacting with the STA COM object, the async method can switch back to the thread pool if desired. @@ -81,8 +81,8 @@ The mitigation for this is to have the method that is executing on the thread po When a component sends many work items to the thread pool in a short timeframe, the queue will grow to store them till one of the thread pool threads can execute them all. Any subsequently queued items will be added to the end of the queue, regardless of their relative priority in the application. When the queue is long, and a work item is appended to the end of the queue that is required for the UI of the application to feel responsive, the application can hang or feel sluggish due to the work that otherwise should be running in the background without impacting UI responsiveness. -The mitigation is for components that have many work items to send to the threadpool to throttle the rate at which new items are introduced to the threadpool to a reasonably small number. This helps keep the queue short, and thus any newly enqueued work will execute much sooner, keeping the application responsive. Throttling work can be done such that the CPU stays busy and the background work moving along quickly, but without sacrificing UI responsiveness. See [this blog post](https://blogs.msdn.microsoft.com/andrewarnottms/2017/05/11/limiting-concurrency-for-faster-and-more-responsive-apps/) for more information on how to easily throttle concurrent work. +The mitigation is for components that have many work items to send to the threadpool to throttle the rate at which new items are introduced to the threadpool to a reasonably small number. This helps keep the queue short, and thus any newly enqueued work will execute much sooner, keeping the application responsive. Throttling work can be done such that the CPU stays busy and the background work moving along quickly, but without sacrificing UI responsiveness. See [this blog post](https://devblogs.microsoft.com/premier-developer/limiting-concurrency-for-faster-and-more-responsive-apps/) for more information on how to easily throttle concurrent work. ## Learn more -Vance Morrison wrote [a blog post](https://blogs.msdn.microsoft.com/vancem/2018/10/16/diagnosing-net-core-threadpool-starvation-with-perfview-why-my-service-is-not-saturating-all-cores-or-seems-to-stall/) describing this situation as well. +Vance Morrison wrote [a blog post](https://learn.microsoft.com/archive/blogs/vancem/diagnosing-net-core-threadpool-starvation-with-perfview-why-my-service-is-not-saturating-all-cores-or-seems-to-stall) describing this situation as well.