From 70d3bec9b8a8a22367024f19627ceceb3f39dbcc Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Sat, 11 Jan 2025 21:26:39 -0700 Subject: [PATCH 1/2] Migrate docs to docfx-based GitHub Pages --- Directory.Build.props | 2 +- README.md | 8 +- doc/analyzers/VSTHRD001.md | 83 +----- doc/analyzers/VSTHRD002.md | 46 +-- doc/analyzers/VSTHRD003.md | 268 +----------------- doc/analyzers/VSTHRD004.md | 44 +-- doc/analyzers/VSTHRD010.md | 62 +--- doc/analyzers/VSTHRD011.md | 58 +--- doc/analyzers/VSTHRD012.md | 34 +-- doc/analyzers/VSTHRD100.md | 57 +--- doc/analyzers/VSTHRD101.md | 72 +---- doc/analyzers/VSTHRD102.md | 57 +--- doc/analyzers/VSTHRD103.md | 30 +- doc/analyzers/VSTHRD104.md | 37 +-- doc/analyzers/VSTHRD105.md | 84 +----- doc/analyzers/VSTHRD106.md | 32 +-- doc/analyzers/VSTHRD107.md | 28 +- doc/analyzers/VSTHRD108.md | 47 +-- doc/analyzers/VSTHRD109.md | 30 +- doc/analyzers/VSTHRD110.md | 76 +---- doc/analyzers/VSTHRD111.md | 39 +-- doc/analyzers/VSTHRD112.md | 67 +---- doc/analyzers/VSTHRD113.md | 38 +-- doc/analyzers/VSTHRD114.md | 32 +-- doc/analyzers/VSTHRD115.md | 39 +-- doc/analyzers/VSTHRD200.md | 48 +--- .../AppWithMainThread.editorconfig | 0 .../AppWithoutMainThread.editorconfig | 0 .../JTFFocusedLibrary.editorconfig | 0 doc/{analyzers => }/editorconfigs/README.md | 2 +- .../editorconfigs/SharedLibrary.editorconfig | 0 .../editorconfigs/Tests.editorconfig | 0 doc/index.md | 14 - docfx/analyzers/VSTHRD001.md | 82 ++++++ docfx/analyzers/VSTHRD002.md | 45 +++ docfx/analyzers/VSTHRD003.md | 267 +++++++++++++++++ docfx/analyzers/VSTHRD004.md | 43 +++ docfx/analyzers/VSTHRD010.md | 61 ++++ docfx/analyzers/VSTHRD011.md | 57 ++++ docfx/analyzers/VSTHRD012.md | 33 +++ docfx/analyzers/VSTHRD100.md | 56 ++++ docfx/analyzers/VSTHRD101.md | 71 +++++ docfx/analyzers/VSTHRD102.md | 56 ++++ docfx/analyzers/VSTHRD103.md | 29 ++ docfx/analyzers/VSTHRD104.md | 36 +++ docfx/analyzers/VSTHRD105.md | 83 ++++++ docfx/analyzers/VSTHRD106.md | 31 ++ docfx/analyzers/VSTHRD107.md | 27 ++ docfx/analyzers/VSTHRD108.md | 46 +++ docfx/analyzers/VSTHRD109.md | 29 ++ docfx/analyzers/VSTHRD110.md | 75 +++++ docfx/analyzers/VSTHRD111.md | 38 +++ docfx/analyzers/VSTHRD112.md | 66 +++++ docfx/analyzers/VSTHRD113.md | 37 +++ docfx/analyzers/VSTHRD114.md | 31 ++ docfx/analyzers/VSTHRD115.md | 38 +++ docfx/analyzers/VSTHRD200.md | 47 +++ {doc => docfx}/analyzers/configuration.md | 0 {doc => docfx}/analyzers/fsa.md | 2 +- {doc => docfx}/analyzers/index.md | 20 +- {doc => docfx}/analyzers/installation.md | 2 +- docfx/analyzers/toc.yml | 29 ++ {doc => docfx/docs}/async_hang.md | 0 {doc => docfx/docs}/cookbook_vs.md | 0 {doc => docfx/docs}/dumpasync.md | 0 docfx/docs/features.md | 25 +- docfx/docs/getting-started.md | 6 +- {doc => docfx/docs}/library_with_jtf.md | 2 +- {doc => docfx/docs}/testing_vs.md | 0 {doc => docfx/docs}/threading_rules.md | 68 +++-- {doc => docfx/docs}/threadpool_starvation.md | 6 +- docfx/docs/toc.yml | 13 +- {doc => docfx}/images/blocked_time.png | Bin .../images/blocked_time_callers.png | Bin ...u_stacks_showing_threadpool_starvation.png | Bin {doc/analyzers => docfx}/images/fsa.png | Bin .../images/vs_threadpoolstarvation_event.jpg | Bin docfx/index.md | 5 +- docfx/toc.yml | 4 + .../README.md | 2 +- .../Utils.cs | 2 +- .../README.md | 2 + 82 files changed, 1540 insertions(+), 1466 deletions(-) rename doc/{analyzers => }/editorconfigs/AppWithMainThread.editorconfig (100%) rename doc/{analyzers => }/editorconfigs/AppWithoutMainThread.editorconfig (100%) rename doc/{analyzers => }/editorconfigs/JTFFocusedLibrary.editorconfig (100%) rename doc/{analyzers => }/editorconfigs/README.md (97%) rename doc/{analyzers => }/editorconfigs/SharedLibrary.editorconfig (100%) rename doc/{analyzers => }/editorconfigs/Tests.editorconfig (100%) delete mode 100644 doc/index.md create mode 100644 docfx/analyzers/VSTHRD001.md create mode 100644 docfx/analyzers/VSTHRD002.md create mode 100644 docfx/analyzers/VSTHRD003.md create mode 100644 docfx/analyzers/VSTHRD004.md create mode 100644 docfx/analyzers/VSTHRD010.md create mode 100644 docfx/analyzers/VSTHRD011.md create mode 100644 docfx/analyzers/VSTHRD012.md create mode 100644 docfx/analyzers/VSTHRD100.md create mode 100644 docfx/analyzers/VSTHRD101.md create mode 100644 docfx/analyzers/VSTHRD102.md create mode 100644 docfx/analyzers/VSTHRD103.md create mode 100644 docfx/analyzers/VSTHRD104.md create mode 100644 docfx/analyzers/VSTHRD105.md create mode 100644 docfx/analyzers/VSTHRD106.md create mode 100644 docfx/analyzers/VSTHRD107.md create mode 100644 docfx/analyzers/VSTHRD108.md create mode 100644 docfx/analyzers/VSTHRD109.md create mode 100644 docfx/analyzers/VSTHRD110.md create mode 100644 docfx/analyzers/VSTHRD111.md create mode 100644 docfx/analyzers/VSTHRD112.md create mode 100644 docfx/analyzers/VSTHRD113.md create mode 100644 docfx/analyzers/VSTHRD114.md create mode 100644 docfx/analyzers/VSTHRD115.md create mode 100644 docfx/analyzers/VSTHRD200.md rename {doc => docfx}/analyzers/configuration.md (100%) rename {doc => docfx}/analyzers/fsa.md (91%) rename {doc => docfx}/analyzers/index.md (81%) rename {doc => docfx}/analyzers/installation.md (96%) create mode 100644 docfx/analyzers/toc.yml rename {doc => docfx/docs}/async_hang.md (100%) rename {doc => docfx/docs}/cookbook_vs.md (100%) rename {doc => docfx/docs}/dumpasync.md (100%) rename {doc => docfx/docs}/library_with_jtf.md (98%) rename {doc => docfx/docs}/testing_vs.md (100%) rename {doc => docfx/docs}/threading_rules.md (85%) rename {doc => docfx/docs}/threadpool_starvation.md (96%) rename {doc => docfx}/images/blocked_time.png (100%) rename {doc => docfx}/images/blocked_time_callers.png (100%) rename {doc => docfx}/images/cpu_stacks_showing_threadpool_starvation.png (100%) rename {doc/analyzers => docfx}/images/fsa.png (100%) rename {doc => docfx}/images/vs_threadpoolstarvation_event.jpg (100%) diff --git a/Directory.Build.props b/Directory.Build.props index e41668d8e..dad792fba 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -24,7 +24,7 @@ embedded - https://github.com/Microsoft/vs-threading + https://microsoft.github.io/vs-threading/ Microsoft Microsoft © Microsoft Corporation. All rights reserved. diff --git a/README.md b/README.md index 97da5667f..689fbf5d2 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ 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). -[Overview documentation](doc/index.md). +[Getting started](https://microsoft.github.io/vs-threading/docs/getting-started.md). -[See the full list of features](src/Microsoft.VisualStudio.Threading/README.md). +[See the full list of features](https://microsoft.github.io/vs-threading/docs/features.md). ## Microsoft.VisualStudio.Threading.Analyzers @@ -19,6 +19,4 @@ Async synchronization primitives, async collections, TPL and dataflow extensions Static code analyzer to detect common mistakes or potential issues regarding threading and async coding. -[Diagnostic analyzer rules](doc/analyzers/index.md). - -[See the full list of features](src/Microsoft.VisualStudio.Threading.Analyzers.CodeFixes/README.md). +[Diagnostic analyzer rules](https://microsoft.github.io/vs-threading/analyzers/index.md). diff --git a/doc/analyzers/VSTHRD001.md b/doc/analyzers/VSTHRD001.md index 1dd47c1ac..0bd13cf04 100644 --- a/doc/analyzers/VSTHRD001.md +++ b/doc/analyzers/VSTHRD001.md @@ -1,82 +1 @@ -# VSTHRD001 Avoid legacy thread switching methods - -Switching to the UI thread should be done using `JoinableTaskFactory.SwitchToMainThreadAsync` -rather than legacy methods such as `Dispatcher.Invoke` or `ThreadHelper.Invoke`. -This avoids deadlocks and can reduce threadpool starvation. - -## Examples of patterns that are flagged by this analyzer - -```csharp -ThreadHelper.Generic.Invoke(delegate { - DoSomething(); -}); -``` - -or - -```cs -Dispatcher.CurrentDispatcher.BeginInvoke(delegate { - DoSomething(); -}); -``` - -## Solution - -Use `await SwitchToMainThreadAsync()` instead, wrapping with the `JoinableTaskFactory`'s `Run` or `RunAsync` method if necessary: - -```csharp -void Foo() { - ThreadHelper.JoinableTaskFactory.Run(async delegate { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - DoSomething(); - }); -} -``` - -In the above example, we obtain a `JoinableTaskFactory` instance from the `ThreadHelper.JoinableTaskFactory` static property -as it exists within Visual Studio itself. Other applications should create and expose their own `JoinableTaskContext` and/or `JoinableTaskFactory` for use in code that run in these applications. -See our doc on [consuming `JoinableTaskFactory` from a library](https://github.com/microsoft/vs-threading/blob/main/doc/library_with_jtf.md) for more information. - -### Replacing Dispatcher.BeginInvoke - -When updating calls to `Dispatcher.BeginInvoke`, there are a few considerations to consider. - -1. `BeginInvoke` schedules the delegate for execution later. -1. `BeginInvoke` always executes the delegate on the dispatcher's thread. -1. `BeginInvoke` schedules the delegate at some given priority, or default priority determined by the dispatcher. - -To resolve a warning for such code, it is often sufficient to replace it with this, which is *roughly* equivalent: - -```cs -await joinableTaskFactory.RunAsync(async delegate { - await joinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true); - DoSomething(); -}) -``` - -The first line in the delegate is necessary to match the behaviors of 1 and 2 on the above list. -When the caller is known to already be on the main thread, you can simplify it slightly to this: - -```cs -await joinableTaskFactory.RunAsync(async delegate { - await Task.Yield(); - DoSomething(); -}) -``` - -Matching behavior 3 on the list above may be important when the dispatcher priority is specified in the BeginInvoke call and was chosen for a particular reason. -In such a case, you can ensure that `JoinableTaskFactory` matches that priority instead of using its default by creating a special `JoinableTaskFactory` instance with the priority setting you require using the [`JoinableTaskFactory.WithPriority`](https://learn.microsoft.com/dotnet/api/microsoft.visualstudio.threading.dispatcherextensions.withpriority?view=visualstudiosdk-2022) method. - -Altogether, this might look like: - -```cs -await joinableTaskFactory.WithPriority(DispatcherPriority.DataBind).RunAsync(async delegate { - await joinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true); - DoSomething(); -}) -``` - -## Configuration - -This analyzer is configurable via the `vs-threading.LegacyThreadSwitchingMembers.txt` file. -See our [configuration](configuration.md) topic for more information. +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD001.html). diff --git a/doc/analyzers/VSTHRD002.md b/doc/analyzers/VSTHRD002.md index 6a05a94ca..69a4fc3c9 100644 --- a/doc/analyzers/VSTHRD002.md +++ b/doc/analyzers/VSTHRD002.md @@ -1,45 +1 @@ -# VSTHRD002 Avoid problematic synchronous waits - -Synchronously waiting on `Task`, `ValueTask`, or awaiters is dangerous and may cause dead locks. - -## Examples of patterns that are flagged by this analyzer - -```csharp -void DoSomething() -{ - DoSomethingElseAsync().Wait(); - DoSomethingElseAsync().GetAwaiter().GetResult(); - var result = CalculateSomethingAsync().Result; -} -``` - -## Solution - -Please consider the following options: - -1. Switch to asynchronous wait if the caller is already a "async" method. -1. Change the chain of callers to be "async" methods, and then change this code to be asynchronous await. -1. Use `JoinableTaskFactory.Run()` to wait on the tasks or awaiters. - -```csharp -async Task DoSomethingAsync() -{ - await DoSomethingElseAsync(); - await DoSomethingElseAsync(); - var result = await CalculateSomethingAsync(); -} - -void DoSomething() -{ - joinableTaskFactory.Run(async delegate - { - await DoSomethingElseAsync(); - await DoSomethingElseAsync(); - var result = await CalculateSomethingAsync(); - }); -} -``` - -Refer to [Asynchronous and multithreaded programming within VS using the JoinableTaskFactory][1] for more information. - -[1]: https://devblogs.microsoft.com/premier-developer/asynchronous-and-multithreaded-programming-within-vs-using-the-joinabletaskfactory/ +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD002.html). diff --git a/doc/analyzers/VSTHRD003.md b/doc/analyzers/VSTHRD003.md index 49d8ad87b..34aba3d45 100644 --- a/doc/analyzers/VSTHRD003.md +++ b/doc/analyzers/VSTHRD003.md @@ -1,267 +1 @@ -# VSTHRD003 Avoid awaiting foreign Tasks - -Tasks that are created and run from another context (not within the currently running method or delegate) -should not be returned or awaited on. Doing so can result in deadlocks because awaiting a `Task` -does not result in the awaiter "joining" the effort such that access to the main thread is shared. -If the awaited `Task` requires the main thread, and the caller that is awaiting it is blocking the -main thread, a deadlock will result. - -When required to await a task that was started earlier, start it within a delegate passed to -`JoinableTaskFactory.RunAsync`, storing the resulting `JoinableTask` in a field or variable. -You can safely await the `JoinableTask` later. - -## Simple examples of patterns that are flagged by this analyzer - -The following example would likely deadlock if `MyMethod` were called on the main thread, -since `SomeOperationAsync` cannot gain access to the main thread in order to complete. - -```csharp -void MyMethod() -{ - System.Threading.Tasks.Task task = SomeOperationAsync(); - joinableTaskFactory.Run(async delegate - { - await task; /* This analyzer will report warning on this line. */ - }); -} -``` - -In the next example, `WaitForMyMethod` may deadlock when `this.task` has not completed -and needs the main thread to complete. - -```csharp -class SomeClass -{ - System.Threading.Tasks.Task task; - - SomeClass() - { - this.task = SomeOperationAsync(); - } - - async Task MyMethodAsync() - { - await this.task; /* This analyzer will report warning on this line. */ - } - - void WaitForMyMethod() - { - joinableTaskFactory.Run(() => MyMethodAsync()); - } -} -``` - -More [advanced examples](#advanced-cases) are further down in this document, below the solution section for the simpler examples. - -## Solution for simpler cases - -To await the result of an async method from with a JoinableTaskFactory.Run delegate, -invoke the async method within the JoinableTaskFactory.Run delegate: - -```csharp -void MyMethod() -{ - joinableTaskFactory.Run(async delegate - { - System.Threading.Tasks.Task task = SomeOperationAsync(); - await task; - }); -} -``` - -Alternatively wrap the original method invocation with JoinableTaskFactory.RunAsync: - -```csharp -class SomeClass -{ - JoinableTask joinableTask; - - SomeClass() - { - this.joinableTask = joinableTaskFactory.RunAsync(() => SomeOperationAsync()); - } - - async Task MyMethodAsync() - { - await this.joinableTask; - } - - void WaitForMyMethod() - { - joinableTaskFactory.Run(() => MyMethodAsync()); - } -} -``` - -## Advanced cases - -### `TaskCompletionSource` - -In the next example, a `TaskCompletionSource` is used as a black-box for unblocking functionality. -It too represents awaiting a foreign task: - -```cs -class SomeClass -{ - TaskCompletionSource tcs = new(); - - public async Task MyMethodAsync() - { - await this.tcs.Task; /* This analyzer will report warning on this line. */ - /* do more stuff */ - } - - void UnlockProgress() - { - this.tcs.TrySetResult(true); - } -} -``` - -The problem with the above code is that `MyMethodAsync()` waits for unknown work (whatever work will lead to the completion of the `TaskCompletionSource`) before making progress. -If `UnlockProgress()` is never called, the caller of `MyMethodAsync()` will be awaiting forever. -Now suppose that the caller of `MyMethodAsync()` is actually inside a `JoinableTaskFactory.Run` delegate: - -```cs -void SomeCaller() -{ - joinableTaskFactory.Run(async delegate - { - await someClass.MyMethodAsync(); - }); -} -``` - -If `SomeCaller()` runs on the main thread, then it will effectively block the main thread while waiting for `this.tcs.Task` from `SomeClass` to complete. -Now suppose that another thread comes along and wants to do some work before calling `UnlockProgress()`: - -```cs -partial class SomeClass -{ - async Task KeyMasterAsync() - { - await joinableTaskFactory.SwitchToMainThreadAsync(); - // do some work - // Unblock others - someClass.UnlockProgress(); - } -} -``` - -We have a deadlock, because `SomeCaller()` is blocking the main thread while waiting for `UnlockProgress()` to be called, but `UnlockProgress()` will not be called until `KeyMasterAsync` can reach the main thread. - -Fixing this fundamentally means that `SomeCaller` will need to *join* whatever work may be needed to ultimately call `UnlockProgress`. But for `SomeCaller`, that work is unknown, since it's at least partially inside another class. -`TaskCompletionSource` is fundamentally a blackbox and the most difficult thing to use correctly while avoiding deadlocks. - -Preferred solutions involve replacing `TaskCompletionSource` with another type that makes tracking the work involved automatic. -These include: - -1. Use `JoinableTaskFactory.RunAsync` and store the resulting `JoinableTask` in a field to await later. -1. Use `AsyncLazy` for one-time init work that should only start if required. Be sure to pass in a `JoinableTaskFactory` instance to its constructor. - -Assuming you must keep using `TaskCompletionSource` though, here's how it can be done as safely as possible. -Joining a set of unknown work is best done with the `JoinableTaskCollection` class. -It is the responsibility of `SomeClass` in the example above to work with this collection to avoid deadlocks, like this: - -```cs -class SomeClass -{ - TaskCompletionSource tcs = new(); - JoinableTaskCollection jtc; - JoinableTaskFactory jtf; - - internal SomeClass(JoinableTaskContext joinableTaskContext) - { - this.jtc = joinableTaskContext.CreateCollection(); - this.jtf = joinableTaskContext.CreateFactory(this.jtc); - } - - public async Task MyMethodAsync() - { - // Our caller is interested in completion of the TaskCompletionSource, - // so join the collected effort while waiting, to avoid deadlocks. - using (this.jtc.Join()) - { - await this.tcs.Task; /* This analyzer will report warning on this line. */ - } - - /* do more stuff */ - } - - void UnlockProgress() - { - this.tcs.TrySetResult(true); - } - - async Task KeyMasterAsync() - { - // As this method must complete to signal the TaskCompletionSource, - // all of its work must be done within the context of a JoinableTask - // that belongs to the JoinableTaskCollection. - // jtf.RunAsync will add the JoinableTask it creates to the jtc collection - // because jtf was created with jtc as an argument in our constructor. - await this.jtf.RunAsync(async delegate - { - // Because we're in the jtc collection, anyone waiting on MyMethodAsync - // will automatically lend us use of the main thread if they have it - // to avoid deadlocks. - // It does NOT matter whether we use jtf or another JoinableTaskFactory instance - // at this point. - await anyOldJTF.SwitchToMainThreadAsync(); - - // do some work - // Unblock others - this.UnlockProgress(); - }); - } -} -``` - -Notice how the public API of the class does not need to expose any `JoinableTask`-related types. -It's an implementation detail of the class. - -This works fine when the class itself fully controls the work to complete the `TaskCompletionSource`. -When _other_ classes also do work (independently of work started within `SomeClass`), the placement and access to the `JoinableTaskFactory` that is associated with the `JoinableTaskCollection` may need to be elevated so that other classes can access it as well so that *all* the work required to complete the `TaskCompletionSource` will be tracked. - -### Task chaining or other means to ensure sequential execution - -Task chaining is another technique that can lead to deadlocks. -Task chaining is where a single `Task` is kept in a field and used to call `Task.ContinueWith` to append another Task, and the resulting Task is then assigned to the field, like this: - -```cs -class TaskChainingExample -{ - private readonly object lockObject = new(); - private Task lastTask = Task.CompletedTask; - - internal Task AddWorkToEnd(Funk work) - { - lock (this.lockObject) - { - return this.lastTask = this.lastTask.ContinueWith(_ => work()).Unwrap(); - } - } -} -``` - -(Note: The above example has several *other* issues that would require more code to address, but it illustrates the idea of task chaining.) - -The deadlock risk with task chaining is that again, the chain of tasks come together to form a kind of private queue which the `JoinableTaskFactory` has no visibility into. -When a task is not at the front of the queue but its owner blocks the main thread for its completion, and if any other task ahead of it in the queue needs the main thread, a deadlock will result. - -For this reason (and several others), task chaining is *not* recommended. -Instead, you can achieve a thread-safe queue that executes work sequentially by utilizing the `ReentrantSemaphore` class. - -Fixing the above example would translate to this (allowing for a variety of reentrancy modes): - -```cs -class SequentialExecutingQueueExample -{ - private readonly ReentrantSemaphore semaphore = ReentrantSemaphore.Create(initialCount: 1, joinableTaskContext, ReentrancyMode.Stack); - - internal Task AddWorkToEnd(Func work) - { - return semaphore.ExecuteAsync(work); - } -} -``` +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD003.html). diff --git a/doc/analyzers/VSTHRD004.md b/doc/analyzers/VSTHRD004.md index 39d3bbdf6..8c937964f 100644 --- a/doc/analyzers/VSTHRD004.md +++ b/doc/analyzers/VSTHRD004.md @@ -1,43 +1 @@ -# VSTHRD004 Await SwitchToMainThreadAsync - -Calls to `JoinableTaskFactory.SwitchToMainThreadAsync` must be awaited -or it is a no-op. - -## Examples of patterns that are flagged by this analyzer - -```csharp -void MyMethod() -{ - joinableTaskFactory.SwitchToMainThreadAsync(); - UIThreadBoundWork(); -} -``` - -## Solution - -Add `await` in front of the call to `JoinableTaskFactory.SwitchToMainThreadAsync`. - -This requires an async context. Here, we fix the problem by making the outer method async: - -```csharp -async Task MyMethodAsync() -{ - await joinableTaskFactory.SwitchToMainThreadAsync(); - UIThreadBoundWork(); -} -``` - - -Alternatively if found in a synchronous method that cannot be made async, -this failure can be fixed by lifting the code into a delegate passed to `JoinableTaskFactory.Run`: - -```csharp -void MyMethod() -{ - joinableTaskFactory.Run(async delegate - { - await joinableTaskFactory.SwitchToMainThreadAsync(); - UIThreadBoundWork(); - }); -} -``` +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD004.html). diff --git a/doc/analyzers/VSTHRD010.md b/doc/analyzers/VSTHRD010.md index bfb01c11d..15a5d10b3 100644 --- a/doc/analyzers/VSTHRD010.md +++ b/doc/analyzers/VSTHRD010.md @@ -1,61 +1 @@ -# VSTHRD010 Invoke single-threaded types on Main thread - -Acquiring, casting, or invoking single-threaded objects should be done after ensuring -that your code is running on the main thread. - -This analyzer can be configured to: -1. Recognize the objects that are single-threaded that are unique to your app or library. -2. Recognize synchronous methods that verify the caller is already on the main thread. -3. Recognize methods that switch to the main thread when the caller awaits them. - Calls to `JoinableTaskFactory.SwitchToMainThreadAsync` methods are pre-configured. - -See our [configuration](configuration.md) topic to learn more about customizing this analyzer. - -This analyzer also recognizes requirements to use the main thread transitively within your solution. -For example, if method `A()` invokes a type that we know from configuration requires the main thread, -and `B()` calls `A()`, then the `B` method also needs the UI thread transitively. -This analyzer flags `B()` as needing to call a method that throws if not already on the main thread -only when `A()` is written to call such a method. - -**NOTE:** This analyzer requires [full solution analysis](fsa.md). - -## Examples of patterns that are flagged by this analyzer - -This example is based on the configuration available from the Visual Studio SDK -that defines `IVs*` interfaces as requiring the main thread. - -```csharp -private void CallVS() -{ - IVsSolution sln = GetIVsSolution(); - sln.SetProperty(); // This analyzer will report warning on this invocation. -} -``` - -## Solution - -First ensure you are running on the main thread before interacting with single-threaded objects. -Either throw when you are not on the appropriate thread, or explicitly switch to the -main thread. - -This solution example is based on the configuration available from the Visual Studio SDK -that defines `ThreadHelper.ThrowIfNotOnUIThread()` as one which throws if the caller -is not already on the main thread. - -```csharp -private void CallVS() -{ - ThreadHelper.ThrowIfNotOnUIThread(); - IVsSolution sln = GetIVsSolution(); - sln.SetProperty(); // This analyzer will not report warning on this invocation. -} - -private async Task CallVSAsync() -{ - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - IVsSolution sln = GetIVsSolution(); - sln.SetProperty(); // This analyzer will not report warning on this invocation. -} -``` - -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. +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD010.html). diff --git a/doc/analyzers/VSTHRD011.md b/doc/analyzers/VSTHRD011.md index 40b7e94fe..8bd47dc63 100644 --- a/doc/analyzers/VSTHRD011.md +++ b/doc/analyzers/VSTHRD011.md @@ -1,57 +1 @@ -# VSTHRD011 Use `AsyncLazy` - -The `Lazy` type executes the value factory just once and -the value factory inherits the context of the first one to request the -`Lazy.Value` property's value. This can lead to deadlocks when -the value factory attempts to switch to the main thread. - -## Examples of patterns that are flagged by this analyzer - -### Using `Lazy` where `T` is `Task` - -When `T` is `Task` (because the value factory is an async method), -if the first caller had no access to the main thread, and the value factory -requires it, it will block. If later a second caller calls the `Value` property -and that second caller is blocking the UI thread for its result, it will deadlock. - -```csharp -var lazy = new Lazy>(async delegate // analyzer flags this line -{ - await Task.Yield(); - return 3; -}); - -int value = await lazy.Value; -``` - -### Using synchronously blocking methods in `Lazy` value factories - -When the value factory passed to the `Lazy` constructor calls synchronously -blocking methods such as `JoinableTaskFactory.Run`, only the first caller -can help any required transition to the main thread. - -```csharp -var lazy = new Lazy(delegate -{ - return joinableTaskFactory.Run(async delegate { // analyzer flags this line - int result = await SomeAsyncMethod(); - return result + 3; - }); -}); - -int value = lazy.Value; -``` - -## Solution - -Use `AsyncLazy` with an async value factory: - -```csharp -var lazy = new AsyncLazy(async delegate -{ - await Task.Yield(); - return 3; -}); - -int value = await lazy.GetValueAsync(); -``` +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD011.html). diff --git a/doc/analyzers/VSTHRD012.md b/doc/analyzers/VSTHRD012.md index 31710a1cb..7c2578ea7 100644 --- a/doc/analyzers/VSTHRD012.md +++ b/doc/analyzers/VSTHRD012.md @@ -1,33 +1 @@ -# VSTHRD012 Provide `JoinableTaskFactory` where allowed - -When constructing types or calling methods that accept a `JoinableTaskFactory` -or `JoinableTaskContext`, take the opportunity to supply one if your application -has a main thread with a single threaded `SynchronizationContext` such as WPF or WinForms. - -## Examples of patterns that are flagged by this analyzer - -```csharp -void F() { - var o = new AsyncLazy(() => Task.FromResult(1)); // analyzer flags this line -} -``` - -## Solution - -Call the overload that accepts a `JoinableTaskFactory` or `JoinableTaskContext` instance: - -```csharp -void F() { - var o = new AsyncLazy(() => Task.FromResult(1), this.JoinableTaskFactory); -} -``` - -## Suppression - -You can suppress the diagnostic by explicitly specifying `null` for the argument: - -```csharp -void F() { - var o = new AsyncLazy(() => Task.FromResult(1), null); -} -``` +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD012.html). diff --git a/doc/analyzers/VSTHRD100.md b/doc/analyzers/VSTHRD100.md index b1a7278d5..25d170bfe 100644 --- a/doc/analyzers/VSTHRD100.md +++ b/doc/analyzers/VSTHRD100.md @@ -1,56 +1 @@ -# VSTHRD100 Avoid `async void` methods - -Methods with `async void` signatures make it impossible for their caller to track -the entire asynchronous operation and handle exceptions that may be thrown by that method. -If the method throws an exception, it crashes the process. - -## Examples of patterns that are flagged by this analyzer - -```csharp -async void DoSomethingAsync() -{ - await SomethingElseAsync(); -} -``` - -## Solution - -Change the method to return `Task` instead of `void`. - -```csharp -async Task DoSomethingAsync() -{ - await SomethingElseAsync(); -} -``` - -A code fix is offered that automatically changes the return type of the method. - -### Event handlers - -For event handlers, avoid `async void` by using `RunAsync`: -```csharp -obj.Event += (s, e) => joinableTaskFactory.RunAsync(() => OnEventAsync(s, e)); -} - -private async Task OnEventAsync(object sender, EventArgs e) -{ - // async code here. -} -``` - -When using method group syntax as an argument, you can define the method with the required signature, without the `async` modifier, and define an anonymous delegate or lambda within the method, like this: - -```cs -var menuItem = new MenuCommand(HandleEvent, commandId); - -private void HandleEvent(object sender, EventArgs e) -{ - _ = joinableTaskFactory.RunAsync(async () => - { - // async code - }); -} -``` - -Refer to [Async/Await - Best Practices in Asynchronous Programming](https://msdn.microsoft.com/en-us/magazine/jj991977.aspx) for more info. +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD100.html). diff --git a/doc/analyzers/VSTHRD101.md b/doc/analyzers/VSTHRD101.md index 10f9e411d..1c842e532 100644 --- a/doc/analyzers/VSTHRD101.md +++ b/doc/analyzers/VSTHRD101.md @@ -1,71 +1 @@ -# VSTHRD101 Avoid unsupported async delegates - -C# allows you to define async delegates or lambdas and use them in contexts that accept -void-returning delegates, thus creating an `async void` method such as is forbidden by -[VSTHRD100](VSTHRD100.md), but is much harder to catch when simply looking at the code -because for the same syntax, the C# compiler will create an `async Func` delegate -or an `async void` delegate based on the type expected by the method being invoked. - -This analyzer helps prevent inadvertent creation of `async void` delegates. - -## Examples of patterns that are flagged by this analyzer - -```csharp -void StartWatching(ObservableCollection oc) -{ - // This delegate becomes an "async void" method to match the EventHandler delegate type. - oc.CollectionChanged += async () => - { - await Task.Yield(); - }; -} - -void StartWatching(ObservableCollection oc) -{ - // This delegate becomes an "async void" method to match the Action delegate type. - Callback(async () => - { - await Task.Yield(); - }); -} - -void Callback(Action action) -{ - // out of scope of sample -} -``` - -## Solution - -1. Wrap the asynchronous behavior in another method that accepts a `Func` delegate. -1. Change the receiving method's expected delegate type to one that returns a `Task` or `Task`. -1. Implement the delegate synchronously. - -```csharp -void StartWatching(ObservableCollection oc) -{ - oc.CollectionChanged += () => - { - // The outer delegate is synchronous, but kicks off async work via a method that accepts an async delegate. - joinableTaskFactory.RunAsync(async delegate { - await Task.Yield(); - }); - }; -} - -void StartWatching(ObservableCollection oc) -{ - // This delegate becomes an "async Task" method to match the Func delegate type. - Callback(async () => - { - await Task.Yield(); - }); -} - -void Callback(Func action) -{ - // out of scope of sample -} -``` - -Refer to [Async/Await - Best Practices in Asynchronous Programming](https://msdn.microsoft.com/en-us/magazine/jj991977.aspx) for more info. +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD101.html). diff --git a/doc/analyzers/VSTHRD102.md b/doc/analyzers/VSTHRD102.md index 60ba1cb71..4d83049f2 100644 --- a/doc/analyzers/VSTHRD102.md +++ b/doc/analyzers/VSTHRD102.md @@ -1,56 +1 @@ -# VSTHRD102 Implement internal logic asynchronously - -Internal or private methods may be invoked by public methods that are asynchronous. -If the internal method has an opportunity to do work asynchronously, it should do so -in order that async public members can truly be async. - -## Examples of patterns that are flagged by this analyzer - -```csharp -public void PublicMethod() -{ - DoWork(); -} - -public async Task PublicMethodAsync() -{ - DoWork(); - await Task.Yield(); -} - -internal void DoWork() -{ - joinableTaskFactory.Run(async delegate // Analyzer will flag this line - { - await DoSomethingAsync(); - }); -} -``` - -Note how `DoWork()` synchronously blocks for both `PublicMethod()` and `PublicMethodAsync()`. - -## Solution - -Remove the synchronously blocking behavior and make the method async. - -```csharp -public void PublicMethod() -{ - joinableTaskFactory.Run(() => PublicMethodAsync()); -} - -public async Task PublicMethodAsync() -{ - await DoWorkAsync(); - await Task.Yield(); -} - -internal async Task DoWorkAsync() -{ - await DoSomethingAsync(); -} -``` - -Note how `DoWorkAsync()` now allows `PublicMethodAsync()` to do its work asynchronously -while `PublicMethod()` continues to synchronously block, giving your external caller the option -as to whether to do work asynchronously or synchronously. +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD102.html). diff --git a/doc/analyzers/VSTHRD103.md b/doc/analyzers/VSTHRD103.md index 348032057..6a94121a9 100644 --- a/doc/analyzers/VSTHRD103.md +++ b/doc/analyzers/VSTHRD103.md @@ -1,29 +1 @@ -# VSTHRD103 Call async methods when in an async method - -In a method which is already asynchronous, calls to other methods should -be to their async versions, where they exist. - -## Examples of patterns that are flagged by this analyzer - -```csharp -Task DoAsync() -{ - file.Read(buffer, 0, 10); -} -``` - -All methods where an Async-suffixed equivalent exists will produce this warning -when called from a `Task`-returning method. -In addition, calling `Task.Wait()`, `Task.Result` or `Task.GetAwaiter().GetResult()` -will produce this warning. - -## Solution - -Await the async version of the method: - -```csharp -async Task DoAsync() -{ - await file.ReadAsync(buffer, 0, 10); -} -``` +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD103.html). diff --git a/doc/analyzers/VSTHRD104.md b/doc/analyzers/VSTHRD104.md index aabbe5722..41e426712 100644 --- a/doc/analyzers/VSTHRD104.md +++ b/doc/analyzers/VSTHRD104.md @@ -1,36 +1 @@ -# VSTHRD104 Offer async option - -When a publicly accessible method uses `JoinableTaskFactory.Run`, there should be -another way to access the async behavior without synchronously blocking the thread -so that an async caller can be async throughout. - -This rule encourages this pattern by recognizing when some method *Foo* exists and -calls `JoinableTaskFactory.Run` that there is also a method *FooAsync*. -The recommended pattern then is for *Foo* to call *FooAsync* from the delegate -passed to `JoinableTaskFactory.Run` so that the implementation only need be written once. - -## Examples of patterns that are flagged by this analyzer - -```csharp -public void Foo() { - this.joinableTaskFactory.Run(async delegate { - await Task.Yield(); - }); -} -``` - -## Solution - -Add a FooAsync method, and (optionally) call it from the Foo method: - -```csharp -public void Foo() { - this.joinableTaskFactory.Run(async delegate { - await FooAsync(); - }); -} - -public async Task FooAsync() { - await Task.Yield(); -} -``` +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD104.html). diff --git a/doc/analyzers/VSTHRD105.md b/doc/analyzers/VSTHRD105.md index 06c3e34a4..62c8b7a7e 100644 --- a/doc/analyzers/VSTHRD105.md +++ b/doc/analyzers/VSTHRD105.md @@ -1,83 +1 @@ -# VSTHRD105 Avoid method overloads that assume `TaskScheduler.Current` - -Certain methods in the .NET Framework have overloads that allow specifying or omitting -a `TaskScheduler` instance. Always specify one explicitly to avoid the assumed `TaskScheduler.Current` -value, whose behavior is defined by your caller and may vary at runtime. - -The "current" `TaskScheduler` is defined by the one that is executing the currently running code. -But when your code is executing without having been scheduled by a `TaskScheduler` (as is the case with most code), -then the `TaskScheduler.Current` property returns `TaskScheduler.Default` which schedules tasks on the thread pool. -This leads many to incorrectly assume that task scheduling methods such as `StartNew` and `ContinueWith` default -to using the thread pool when in fact their default behavior varies by your caller. - -This variability in behavior leads to bugs when, for example, `TaskScheduler.Current` returns a `TaskScheduler` -that executes tasks on the application's main thread and/or only executes one task at once, such as one obtained -from the `TaskScheduler.FromCurrentSynchronizationContext()` method. -Such a circumstance often leads to deadlocks or responsiveness issues in the application. - -Always explicitly specifying `TaskScheduler.Default` (or other if appropriate) ensures your code will schedule -tasks in a predictable, consistent way. - -No diagnostic is produced by this analyzer when `TaskFactory.StartNew` is invoked on a private instance -of `TaskFactory`, since it may in fact have a safe default for `TaskScheduler`. - -Similar rules: [CA2008 (DoNotCreateTasksWithoutPassingATaskSchedulerAnalyzer)](https://github.com/dotnet/roslyn-analyzers/blob/32d8f1e397439035f0ecb5f61a9e672225f0ecdb/src/Microsoft.NetCore.Analyzers/Core/Tasks/DoNotCreateTasksWithoutPassingATaskScheduler.cs) - -## Examples of patterns that are flagged by this analyzer - -```csharp -private void FirstMethod() -{ - TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); - Task.Factory.StartNew( - () => - { - this.AnotherMethod(); - }, - System.Threading.CancellationToken.None, - TaskCreationOptions.None, - uiScheduler); -} - -private void AnotherMethod() -{ - // TaskScheduler.Current is assumed here, which is determined by our caller. - var nestedTask = Task.Factory.StartNew( // analyzer flags this line - () => - { - // Ooops, we're still on the UI thread when called by FirstMethod. - // But we might be on the thread pool if someone else called us. - }); -} -``` - -## Solution - -Specify a `TaskScheduler` explicitly to suppress the warning: - -```csharp -private void FirstMethod() -{ - TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); - Task.Factory.StartNew( - () => - { - this.AnotherMethod(); - }, - CancellationToken.None, - TaskCreationOptions.None, - uiScheduler); -} - -private void AnotherMethod() -{ - var nestedTask = Task.Factory.StartNew( - () => - { - // Ah, now we're reliably running on the thread pool. :) - }, - CancellationToken.None, - TaskCreationOptions.None, - TaskScheduler.Default); // Specify TaskScheduler explicitly here. -} -``` +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD105.html). diff --git a/doc/analyzers/VSTHRD106.md b/doc/analyzers/VSTHRD106.md index f47fc46ce..8a34ed3aa 100644 --- a/doc/analyzers/VSTHRD106.md +++ b/doc/analyzers/VSTHRD106.md @@ -1,31 +1 @@ -# VSTHRD106 Use `InvokeAsync` to raise async events - -Asynchronous events (those typed as `AsyncEventHandler`) must be raised carefully to ensure -all event handlers are invoked and awaited on. - -Although C# lets you invoke event handlers naturally, it has no awareness of async event handlers -and thus will not let you correctly await on their invocation nor invoke them sequentially. - -## Examples of patterns that are flagged by this analyzer - -```csharp -public AsyncEventHandler Clicked; - -async Task OnClicked() { - await Clicked(this, EventArgs.Empty); // only awaits the first event handler. -} -``` - -## Solution - -Use the `InvokeAsync` extension method defined in the `TplExtensions` class and await its result. -This will ensure each event handler completes before invoking the next event handler in the list, -similar to the default behavior for raising synchronous events. - -```csharp -public AsyncEventHandler Clicked; - -async Task OnClicked() { - await Clicked.InvokeAsync(this, EventArgs.Empty); // await for the completion of all handlers. -} -``` +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD106.html). diff --git a/doc/analyzers/VSTHRD107.md b/doc/analyzers/VSTHRD107.md index 8694cf2ea..76dab4581 100644 --- a/doc/analyzers/VSTHRD107.md +++ b/doc/analyzers/VSTHRD107.md @@ -1,27 +1 @@ -# VSTHRD107 Await Task within using expression - -The C# `using` statement requires that the used expression implement `IDisposable`. -Because `Task` implements `IDisposable`, one may accidentally omit an `await` operator -and `Dispose` of the `Task` instead of the `T` result itself when `T` derives from `IDisposable`. - -## Examples of patterns that are flagged by this analyzer - -```csharp -AsyncSemaphore lck; -using (lck.EnterAsync()) -{ - // ... -} -``` - -## Solution - -Add the `await` operator within the `using` expression. - -```csharp -AsyncSemaphore lck; -using (await lck.EnterAsync()) -{ - // ... -} -``` +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD107.html). diff --git a/doc/analyzers/VSTHRD108.md b/doc/analyzers/VSTHRD108.md index c2b986e2a..9771719f3 100644 --- a/doc/analyzers/VSTHRD108.md +++ b/doc/analyzers/VSTHRD108.md @@ -1,46 +1 @@ -# VSTHRD108 Assert thread affinity unconditionally - -When a method has thread affinity and throws if called from the wrong thread, it should do so without regard to any other condition. This helps ensure the caller will notice early during development that they are calling from the wrong thread. Extra conditions can hide the problem till end users discover an application failure. - -## Examples of patterns that are flagged by this analyzer - -```csharp -private int? age; - -public int GetAge() -{ - if (!this.age.HasValue) - { - ThreadHelper.ThrowIfNotOnUIThread(); - this.age = DoExpensiveUIThreadWork(); - } - - return this.age.Value; -} -``` - -The problem here is that although the UI thread is only strictly required when the field is actually initialized, callers generally cannot predict whether they will be the first or a subsequent caller. If they call from a background thread and tend to be a subsequent caller, no exception will be thrown. But under some conditions in the app when they happen to be the first caller, they'll fail at runtime because they're calling from the background thread. - -## Solution - -Move the code that throws when not on the UI thread outside the conditional block. - -```csharp -private int? age; - -public int GetAge() -{ - ThreadHelper.ThrowIfNotOnUIThread(); - if (!this.age.HasValue) - { - this.age = DoExpensiveUIThreadWork(); - } - - return this.age.Value; -} -``` - -## Configuration - -This analyzer is configurable via the `vs-threading.MainThreadAssertingMethods.txt` file. -See our [configuration](configuration.md) topic for more information. +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD108.html). diff --git a/doc/analyzers/VSTHRD109.md b/doc/analyzers/VSTHRD109.md index 7e28198b7..d0792db4a 100644 --- a/doc/analyzers/VSTHRD109.md +++ b/doc/analyzers/VSTHRD109.md @@ -1,29 +1 @@ -# VSTHRD109 Switch instead of assert in async methods - -Methods that are or can be async should switch to the main thread when necessary -rather than throw an exception if invoked from a different thread. -This allows callers to invoke any async method from any thread -without having to concern themselves with the threading requirements of a method that -can support its own threading requirements by switching. - -## Examples of patterns that are flagged by this analyzer - -```csharp -async Task FooAsync() { - ThreadHelper.ThrowIfNotOnUIThread(); - DoStuff(); - await DoMoreStuff(); -} -``` - -## Solution - -Use `await SwitchToMainThreadAsync()` instead: - -```csharp -async Task FooAsync() { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - DoStuff(); - await DoMoreStuff(); -} -``` +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD109.html). diff --git a/doc/analyzers/VSTHRD110.md b/doc/analyzers/VSTHRD110.md index 7aa764f52..42bb17ab1 100644 --- a/doc/analyzers/VSTHRD110.md +++ b/doc/analyzers/VSTHRD110.md @@ -1,75 +1 @@ -# VSTHRD110 Observe result of async calls - -Tasks returned from async methods should be awaited, or assigned to a variable for observation later. -Methods that return `Task`s often complete and report their work via the `Task` they return, and simply -invoking the method does not guarantee that its work is complete nor successful. Using the `await` keyword -just before the method call causes execution of the calling method to effectively suspend until the called -method has completed and rethrows any exception thrown by the method. - -When a `Task` or `Task` is returned and is not awaited or redirected in some other way, -within the context of a synchronous method, a warning is reported. - -This rule does *not* apply to calls made within async methods, since [CS4014][CS4014] already reports these. - -## Examples of patterns that are flagged by this analyzer - -```csharp -void Foo() { - DoStuffAsync(); -} - -async Task DoStuffAsync() { /* ... */ } -``` - -## Solution - -Convert the method to be async and await the expression: - -```csharp -async Task FooAsync() { - await DoStuffAsync(); -} - -async Task DoStuffAsync() { /* ... */ } -``` - -When the calling method's signature cannot be changed, wrap the method body in a `JoinableTaskFactory.Run` delegate instead: - -```csharp -void Foo() { - jtf.Run(async delegate { - await DoStuffAsync(); - }); -} - -async Task DoStuffAsync() { /* ... */ } -``` - -One other option is to assign the result of the method call to a field or local variable, presumably to track it later: - -```csharp -void Foo() { - Task watchThis = DoStuffAsync(); -} - -async Task DoStuffAsync() { /* ... */ } -``` - -When tracking the `Task` with a field, remember that to await it later without risk of deadlocking, -wrap it in a `JoinableTask` using `JoinableTaskFactory.RunAsync`, per [the 3rd rule](../threading_rules.md#Rule3). - -```csharp -JoinableTask watchThis; - -void Foo() { - this.watchThis = jtf.RunAsync(() => DoStuffAsync()); -} - -async Task WaitForFooToFinishAsync() { - await this.watchThis; -} - -async Task DoStuffAsync() { /* ... */ } -``` - -[CS4014]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs4014 +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD110.html). diff --git a/doc/analyzers/VSTHRD111.md b/doc/analyzers/VSTHRD111.md index 0495e84f0..7cd14fe7a 100644 --- a/doc/analyzers/VSTHRD111.md +++ b/doc/analyzers/VSTHRD111.md @@ -1,38 +1 @@ -# VSTHRD111 Use `.ConfigureAwait(bool)` - -Some code bases, particularly libraries with no affinity to an app's UI thread, are advised to use `.ConfigureAwait(false)` for each and every _await_ because it can avoid deadlocks after those calls start on an application's UI thread and the app later decides to synchronously block the UI thread waiting for those tasks to finish. Using `.ConfigureAwait(false)` also allows continuations to switch to a background thread even when no synchronous blocking would cause a deadlock, which makes for a more responsive application and possibly higher throughput of async operations. - -Note that this scenario can also be solved using the `JoinableTaskFactory`, but many class libraries may not wish to depend on the application proffers an instance of that type to the library. Where JoinableTaskFactory _does_ apply, use of `.ConfigureAwait(false)` is _not_ recommended. See [this topic](https://github.com/Microsoft/vs-threading/blob/main/doc/cookbook_vs.md#should-i-await-a-task-with-configureawaitfalse) for more on when `.ConfigureAwait(false)` and `.ConfigureAwait(true)` are appropriate. - -**This analyzer's diagnostics are *hidden* by default**. You should enable the rule for libraries that use to require this await suffix. - -## Examples of patterns that are flagged by this analyzer - -Any await on `Task` or `ValueTask` without the `.ConfigureAwait(bool)` method called on it will be flagged. - -```csharp -async Task FooAsync() { - await DoStuffAsync(); // This line is flagged - await DoMoreStuffAsync(); // This line is flagged -} - -async Task DoStuffAsync() { /* ... */ } -async ValueTask DoMoreStuffAsync() { /* ... */ } -``` - -## Solution - -Add `.ConfigureAwait(false)` or `.ConfigureAwait(true)` to the awaited `Task` or `ValueTask`. - -```csharp -async Task FooAsync() { - await DoStuffAsync().ConfigureAwait(true); - await DoMoreStuffAsync().ConfigureAwait(false); -} - -async Task DoStuffAsync() { /* ... */ } -async ValueTask DoMoreStuffAsync() { /* ... */ } -``` - -Code fixes are offered for for this diagnostic to add either `.ConfigureAwait(false)` or `.ConfigureAwait(true)` -to an awaited expression. +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD111.html). diff --git a/doc/analyzers/VSTHRD112.md b/doc/analyzers/VSTHRD112.md index b9e3ae45c..edc8f6772 100644 --- a/doc/analyzers/VSTHRD112.md +++ b/doc/analyzers/VSTHRD112.md @@ -1,66 +1 @@ -# VSTHRD112 Implement `System.IAsyncDisposable` - -The `Microsoft.VisualStudio.Threading.IAsyncDisposable` interface is obsolete now that the -`System.IAsyncDisposable` interface has been defined for .NET Standard 2.0 and .NET Framework 4.6.1 -by the [`Microsoft.Bcl.AsyncInterfaces` NuGet package](https://www.nuget.org/packages/Microsoft.Bcl.AsyncInterfaces). - -New classes looking to support async disposable should use `System.IAsyncDisposable` instead of `Microsoft.VisualStudio.Threading.IAsyncDisposable`. -Existing classes that already implement `Microsoft.VisualStudio.Threading.IAsyncDisposable` should *also* implement `System.IAsyncDisposable` so the async disposal option will be recognized by code that only checks for presence of the new interface. - -## Examples of patterns that are flagged by this analyzer - -This class only implements `Microsoft.VisualStudio.Threading.IAsyncDisposable` and will produce the VSTHRD112 diagnostic: - -```cs -using Microsoft.VisualStudio.Threading; - -class SomeClass : IAsyncDisposable -{ - public Task DisposeAsync() - { - } -} -``` - -## Solution - -Implement `System.IAsyncDisposable` in addition to (or instead of) `Microsoft.VisualStudio.Threading.IAsyncDisposable`. -Add a package reference to `Microsoft.Bcl.AsyncInterfaces` if the compiler cannot find `System.IAsyncDisposable`. - -In this example, only `System.IAsyncDisposable` is supported, which is acceptable: - -```cs -using System; - -class SomeClass : IAsyncDisposable -{ - public ValueTask DisposeAsync() - { - } -} -``` - -In this next example, both interfaces are supported: - -```cs -class SomeClass : System.IAsyncDisposable, Microsoft.VisualStudio.Threading.IAsyncDisposable -{ - Task Microsoft.VisualStudio.Threading.IAsyncDisposable.DisposeAsync() - { - // Simply forward the call to the other DisposeAsync overload. - System.IAsyncDisposable self = this; - return self.DisposeAsync().AsTask(); - } - - ValueTask System.IAsyncDisposable.DisposeAsync() - { - // Interesting dispose logic here. - } -} -``` - -In the above example both `DisposeAsync` methods are explicit interface implementations. -Promoting one of the methods to be `public` is typically advised. -If one of these methods was already public and the class itself is public or protected, keep the same method public to avoid an API binary breaking change. - -An automated code fix may be offered for VSTHRD112 diagnostics. +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD112.html). diff --git a/doc/analyzers/VSTHRD113.md b/doc/analyzers/VSTHRD113.md index 8d4dee677..547e22219 100644 --- a/doc/analyzers/VSTHRD113.md +++ b/doc/analyzers/VSTHRD113.md @@ -1,37 +1 @@ -# VSTHRD113 Check for `System.IAsyncDisposable` - -The `Microsoft.VisualStudio.Threading.IAsyncDisposable` interface is obsolete now that the -`System.IAsyncDisposable` interface has been defined for .NET Standard 2.0 and .NET Framework 4.6.1 -by the [`Microsoft.Bcl.AsyncInterfaces` NuGet package](https://www.nuget.org/packages/Microsoft.Bcl.AsyncInterfaces). - -Existing code that tests for the `Microsoft.VisualStudio.Threading.IAsyncDisposable` interface on some object should also check for `System.IAsyncDisposable` and behave similarly in either case. -New code should consider only supporting the new `System.IAsyncDisposable` interface. - -## Examples of patterns that are flagged by this analyzer - -The following code only checks for the obsolete interface and is flagged by this diagnostic: - -```cs -using Microsoft.VisualStudio.Threading; - -if (obj is IAsyncDisposable asyncDisposable) -{ - await asyncDisposable.DisposeAsync(); -} -``` - -## Solution - -Fix this by adding a code branch for the new interface that behaves similarly -within the same containing code block: - -```cs -if (obj is Microsoft.VisualStudio.Threading.IAsyncDisposable vsThreadingAsyncDisposable) -{ - await vsThreadingAsyncDisposable.DisposeAsync(); -} -else if (obj is System.IAsyncDisposable bclAsyncDisposable) -{ - await bclAsyncDisposable.DisposeAsync(); -} -``` +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD113.html). diff --git a/doc/analyzers/VSTHRD114.md b/doc/analyzers/VSTHRD114.md index c20c3fb53..d7822f8ca 100644 --- a/doc/analyzers/VSTHRD114.md +++ b/doc/analyzers/VSTHRD114.md @@ -1,31 +1 @@ -# VSTHRD114 Avoid returning a null Task - -Returning `null` from a non-async `Task`/`Task` method will cause a `NullReferenceException` at runtime. This problem can be avoided by returning `Task.CompletedTask`, `Task.FromResult(null)` or `Task.FromResult(default(T))` instead. - -## Examples of patterns that are flagged by this analyzer - -Any non-async `Task` returning method with an explicit `return null;` will be flagged. - -```csharp -Task DoAsync() { - return null; -} - -Task GetSomethingAsync() { - return null; -} -``` - -## Solution - -Return a task like `Task.CompletedTask` or `Task.FromResult`. - -```csharp -Task DoAsync() { - return Task.CompletedTask; -} - -Task GetSomethingAsync() { - return Task.FromResult(null); -} -``` +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD114.html). diff --git a/doc/analyzers/VSTHRD115.md b/doc/analyzers/VSTHRD115.md index 5e1560911..419b7bf3c 100644 --- a/doc/analyzers/VSTHRD115.md +++ b/doc/analyzers/VSTHRD115.md @@ -1,38 +1 @@ -# VSTHRD115 Avoid creating a JoinableTaskContext with an explicit `null` `SynchronizationContext` - -Constructing a `JoinableTaskContext` with an explicit `null` `SynchronizationContext` is not recommended as a means to construct an instance for use in unit tests or processes without a main thread. -This is because the constructor will automatically use `SynchronizationContext.Current` in lieu of a non-`null` argument. -If `SynchronizationContext.Current` happens to be non-`null`, the constructor may unexpectedly configure the new instance as if a main thread were present. - -## Examples of patterns that are flagged by this analyzer - -```csharp -void SetupJTC() { - this.jtc = new JoinableTaskContext(null, null); -} -``` - -This code *appears* to configure the `JoinableTaskContext` to not be associated with any `SynchronizationContext`. -But in fact it will be associated with the current `SynchronizationContext` if one is present. - -## Solution - -If you intended to inherit `SynchronizationContext.Current` to initialize with a main thread, -provide that value explicitly as the second argument to suppress the warning: - -```cs -void SetupJTC() { - this.jtc = new JoinableTaskContext(null, SynchronizationContext.Current); -} -``` - -If you intended to create a `JoinableTaskContext` for use in a unit test or in a process without a main thread, -call `JoinableTaskContext.CreateNoOpContext()` instead: - -```cs -void SetupJTC() { - this.jtc = JoinableTaskContext.CreateNoOpContext(); -} -``` - -Code fixes are offered to update code to either of the above patterns. +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD115.html). diff --git a/doc/analyzers/VSTHRD200.md b/doc/analyzers/VSTHRD200.md index 3f87cf273..abe7406d3 100644 --- a/doc/analyzers/VSTHRD200.md +++ b/doc/analyzers/VSTHRD200.md @@ -1,47 +1 @@ -# VSTHRD200 Use `Async` suffix for async methods - -The .NET Guidelines for async methods includes that such methods -should have names that include an "Async" suffix. - -Methods that return awaitable types such as `Task` or `ValueTask` -should have an Async suffix. -Methods that do not return awaitable types should not use the Async suffix. - -## Examples of patterns that are flagged by this analyzer - -This `Task`-returning method should have a name that ends with Async: - -```csharp -async Task DoSomething() // analyzer flags this line -{ - await Task.Yield(); -} -``` - -This method should not have a name that ends with Async, since it does not return an awaitable type: - -```csharp -bool DoSomethingElseAsync() // analyzer flags this line -{ - return false; -} -``` - -## Solution - -Simply rename the method to end in "Async" (or remove the suffix, as appropriate): - -```csharp -async Task DoSomethingAsync() -{ - await Task.Yield(); -} - -bool DoSomethingElse() -{ - return false; -} -``` - - -A code fix exists to automatically rename such methods. +This content has been moved to [GitHub Pages](https://microsoft.github.io/vs-threading/analyzers/VSTHRD200.html). diff --git a/doc/analyzers/editorconfigs/AppWithMainThread.editorconfig b/doc/editorconfigs/AppWithMainThread.editorconfig similarity index 100% rename from doc/analyzers/editorconfigs/AppWithMainThread.editorconfig rename to doc/editorconfigs/AppWithMainThread.editorconfig diff --git a/doc/analyzers/editorconfigs/AppWithoutMainThread.editorconfig b/doc/editorconfigs/AppWithoutMainThread.editorconfig similarity index 100% rename from doc/analyzers/editorconfigs/AppWithoutMainThread.editorconfig rename to doc/editorconfigs/AppWithoutMainThread.editorconfig diff --git a/doc/analyzers/editorconfigs/JTFFocusedLibrary.editorconfig b/doc/editorconfigs/JTFFocusedLibrary.editorconfig similarity index 100% rename from doc/analyzers/editorconfigs/JTFFocusedLibrary.editorconfig rename to doc/editorconfigs/JTFFocusedLibrary.editorconfig diff --git a/doc/analyzers/editorconfigs/README.md b/doc/editorconfigs/README.md similarity index 97% rename from doc/analyzers/editorconfigs/README.md rename to doc/editorconfigs/README.md index cc2e2366a..4fbffaa7b 100644 --- a/doc/analyzers/editorconfigs/README.md +++ b/doc/editorconfigs/README.md @@ -25,7 +25,7 @@ While several project types have specific .editorconfig files defined in this fo Libraries that may run in any process, whether they have a main thread or not, should code themselves defensively to avoid any dependency on the main thread so that applications that do not follow `JoinableTaskFactory` rules can avoid deadlocks even when synchronously blocking their main thread using `Task.Wait()` on code running inside your library. In particular, shared libraries of general interest should _always_ use `.ConfigureAwait(false)` when awaiting on tasks. -[Learn more about authoring libraries following best threading practices](../../library_with_jtf.md). +[Learn more about authoring libraries following best threading practices](https://microsoft.github.io/vs-threading/docs/library_with_jtf.md). ### Libraries that run inside a JoinableTaskFactory-compliant application diff --git a/doc/analyzers/editorconfigs/SharedLibrary.editorconfig b/doc/editorconfigs/SharedLibrary.editorconfig similarity index 100% rename from doc/analyzers/editorconfigs/SharedLibrary.editorconfig rename to doc/editorconfigs/SharedLibrary.editorconfig diff --git a/doc/analyzers/editorconfigs/Tests.editorconfig b/doc/editorconfigs/Tests.editorconfig similarity index 100% rename from doc/analyzers/editorconfigs/Tests.editorconfig rename to doc/editorconfigs/Tests.editorconfig diff --git a/doc/index.md b/doc/index.md deleted file mode 100644 index 7c65607ed..000000000 --- a/doc/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# Threading documentation - -## Overview - -* [3 Threading Rules](threading_rules.md) -* [Diagnostic analyzer rules](analyzers/index.md) -* [Cookbook for Visual Studio](cookbook_vs.md) -* [Testing a Visual Studio extension that uses JoinableTaskFactory](testing_vs.md) -* [Authoring a library with a JoinableTaskFactory dependency](library_with_jtf.md) - -## Performance and responsiveness investigation techniques - -* [Async hang investigations](async_hang.md) -* [Investigating Threadpool starvation issues](threadpool_starvation.md) diff --git a/docfx/analyzers/VSTHRD001.md b/docfx/analyzers/VSTHRD001.md new file mode 100644 index 000000000..1dd47c1ac --- /dev/null +++ b/docfx/analyzers/VSTHRD001.md @@ -0,0 +1,82 @@ +# VSTHRD001 Avoid legacy thread switching methods + +Switching to the UI thread should be done using `JoinableTaskFactory.SwitchToMainThreadAsync` +rather than legacy methods such as `Dispatcher.Invoke` or `ThreadHelper.Invoke`. +This avoids deadlocks and can reduce threadpool starvation. + +## Examples of patterns that are flagged by this analyzer + +```csharp +ThreadHelper.Generic.Invoke(delegate { + DoSomething(); +}); +``` + +or + +```cs +Dispatcher.CurrentDispatcher.BeginInvoke(delegate { + DoSomething(); +}); +``` + +## Solution + +Use `await SwitchToMainThreadAsync()` instead, wrapping with the `JoinableTaskFactory`'s `Run` or `RunAsync` method if necessary: + +```csharp +void Foo() { + ThreadHelper.JoinableTaskFactory.Run(async delegate { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + DoSomething(); + }); +} +``` + +In the above example, we obtain a `JoinableTaskFactory` instance from the `ThreadHelper.JoinableTaskFactory` static property +as it exists within Visual Studio itself. Other applications should create and expose their own `JoinableTaskContext` and/or `JoinableTaskFactory` for use in code that run in these applications. +See our doc on [consuming `JoinableTaskFactory` from a library](https://github.com/microsoft/vs-threading/blob/main/doc/library_with_jtf.md) for more information. + +### Replacing Dispatcher.BeginInvoke + +When updating calls to `Dispatcher.BeginInvoke`, there are a few considerations to consider. + +1. `BeginInvoke` schedules the delegate for execution later. +1. `BeginInvoke` always executes the delegate on the dispatcher's thread. +1. `BeginInvoke` schedules the delegate at some given priority, or default priority determined by the dispatcher. + +To resolve a warning for such code, it is often sufficient to replace it with this, which is *roughly* equivalent: + +```cs +await joinableTaskFactory.RunAsync(async delegate { + await joinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true); + DoSomething(); +}) +``` + +The first line in the delegate is necessary to match the behaviors of 1 and 2 on the above list. +When the caller is known to already be on the main thread, you can simplify it slightly to this: + +```cs +await joinableTaskFactory.RunAsync(async delegate { + await Task.Yield(); + DoSomething(); +}) +``` + +Matching behavior 3 on the list above may be important when the dispatcher priority is specified in the BeginInvoke call and was chosen for a particular reason. +In such a case, you can ensure that `JoinableTaskFactory` matches that priority instead of using its default by creating a special `JoinableTaskFactory` instance with the priority setting you require using the [`JoinableTaskFactory.WithPriority`](https://learn.microsoft.com/dotnet/api/microsoft.visualstudio.threading.dispatcherextensions.withpriority?view=visualstudiosdk-2022) method. + +Altogether, this might look like: + +```cs +await joinableTaskFactory.WithPriority(DispatcherPriority.DataBind).RunAsync(async delegate { + await joinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true); + DoSomething(); +}) +``` + +## Configuration + +This analyzer is configurable via the `vs-threading.LegacyThreadSwitchingMembers.txt` file. +See our [configuration](configuration.md) topic for more information. diff --git a/docfx/analyzers/VSTHRD002.md b/docfx/analyzers/VSTHRD002.md new file mode 100644 index 000000000..6a05a94ca --- /dev/null +++ b/docfx/analyzers/VSTHRD002.md @@ -0,0 +1,45 @@ +# VSTHRD002 Avoid problematic synchronous waits + +Synchronously waiting on `Task`, `ValueTask`, or awaiters is dangerous and may cause dead locks. + +## Examples of patterns that are flagged by this analyzer + +```csharp +void DoSomething() +{ + DoSomethingElseAsync().Wait(); + DoSomethingElseAsync().GetAwaiter().GetResult(); + var result = CalculateSomethingAsync().Result; +} +``` + +## Solution + +Please consider the following options: + +1. Switch to asynchronous wait if the caller is already a "async" method. +1. Change the chain of callers to be "async" methods, and then change this code to be asynchronous await. +1. Use `JoinableTaskFactory.Run()` to wait on the tasks or awaiters. + +```csharp +async Task DoSomethingAsync() +{ + await DoSomethingElseAsync(); + await DoSomethingElseAsync(); + var result = await CalculateSomethingAsync(); +} + +void DoSomething() +{ + joinableTaskFactory.Run(async delegate + { + await DoSomethingElseAsync(); + await DoSomethingElseAsync(); + var result = await CalculateSomethingAsync(); + }); +} +``` + +Refer to [Asynchronous and multithreaded programming within VS using the JoinableTaskFactory][1] for more information. + +[1]: https://devblogs.microsoft.com/premier-developer/asynchronous-and-multithreaded-programming-within-vs-using-the-joinabletaskfactory/ diff --git a/docfx/analyzers/VSTHRD003.md b/docfx/analyzers/VSTHRD003.md new file mode 100644 index 000000000..49d8ad87b --- /dev/null +++ b/docfx/analyzers/VSTHRD003.md @@ -0,0 +1,267 @@ +# VSTHRD003 Avoid awaiting foreign Tasks + +Tasks that are created and run from another context (not within the currently running method or delegate) +should not be returned or awaited on. Doing so can result in deadlocks because awaiting a `Task` +does not result in the awaiter "joining" the effort such that access to the main thread is shared. +If the awaited `Task` requires the main thread, and the caller that is awaiting it is blocking the +main thread, a deadlock will result. + +When required to await a task that was started earlier, start it within a delegate passed to +`JoinableTaskFactory.RunAsync`, storing the resulting `JoinableTask` in a field or variable. +You can safely await the `JoinableTask` later. + +## Simple examples of patterns that are flagged by this analyzer + +The following example would likely deadlock if `MyMethod` were called on the main thread, +since `SomeOperationAsync` cannot gain access to the main thread in order to complete. + +```csharp +void MyMethod() +{ + System.Threading.Tasks.Task task = SomeOperationAsync(); + joinableTaskFactory.Run(async delegate + { + await task; /* This analyzer will report warning on this line. */ + }); +} +``` + +In the next example, `WaitForMyMethod` may deadlock when `this.task` has not completed +and needs the main thread to complete. + +```csharp +class SomeClass +{ + System.Threading.Tasks.Task task; + + SomeClass() + { + this.task = SomeOperationAsync(); + } + + async Task MyMethodAsync() + { + await this.task; /* This analyzer will report warning on this line. */ + } + + void WaitForMyMethod() + { + joinableTaskFactory.Run(() => MyMethodAsync()); + } +} +``` + +More [advanced examples](#advanced-cases) are further down in this document, below the solution section for the simpler examples. + +## Solution for simpler cases + +To await the result of an async method from with a JoinableTaskFactory.Run delegate, +invoke the async method within the JoinableTaskFactory.Run delegate: + +```csharp +void MyMethod() +{ + joinableTaskFactory.Run(async delegate + { + System.Threading.Tasks.Task task = SomeOperationAsync(); + await task; + }); +} +``` + +Alternatively wrap the original method invocation with JoinableTaskFactory.RunAsync: + +```csharp +class SomeClass +{ + JoinableTask joinableTask; + + SomeClass() + { + this.joinableTask = joinableTaskFactory.RunAsync(() => SomeOperationAsync()); + } + + async Task MyMethodAsync() + { + await this.joinableTask; + } + + void WaitForMyMethod() + { + joinableTaskFactory.Run(() => MyMethodAsync()); + } +} +``` + +## Advanced cases + +### `TaskCompletionSource` + +In the next example, a `TaskCompletionSource` is used as a black-box for unblocking functionality. +It too represents awaiting a foreign task: + +```cs +class SomeClass +{ + TaskCompletionSource tcs = new(); + + public async Task MyMethodAsync() + { + await this.tcs.Task; /* This analyzer will report warning on this line. */ + /* do more stuff */ + } + + void UnlockProgress() + { + this.tcs.TrySetResult(true); + } +} +``` + +The problem with the above code is that `MyMethodAsync()` waits for unknown work (whatever work will lead to the completion of the `TaskCompletionSource`) before making progress. +If `UnlockProgress()` is never called, the caller of `MyMethodAsync()` will be awaiting forever. +Now suppose that the caller of `MyMethodAsync()` is actually inside a `JoinableTaskFactory.Run` delegate: + +```cs +void SomeCaller() +{ + joinableTaskFactory.Run(async delegate + { + await someClass.MyMethodAsync(); + }); +} +``` + +If `SomeCaller()` runs on the main thread, then it will effectively block the main thread while waiting for `this.tcs.Task` from `SomeClass` to complete. +Now suppose that another thread comes along and wants to do some work before calling `UnlockProgress()`: + +```cs +partial class SomeClass +{ + async Task KeyMasterAsync() + { + await joinableTaskFactory.SwitchToMainThreadAsync(); + // do some work + // Unblock others + someClass.UnlockProgress(); + } +} +``` + +We have a deadlock, because `SomeCaller()` is blocking the main thread while waiting for `UnlockProgress()` to be called, but `UnlockProgress()` will not be called until `KeyMasterAsync` can reach the main thread. + +Fixing this fundamentally means that `SomeCaller` will need to *join* whatever work may be needed to ultimately call `UnlockProgress`. But for `SomeCaller`, that work is unknown, since it's at least partially inside another class. +`TaskCompletionSource` is fundamentally a blackbox and the most difficult thing to use correctly while avoiding deadlocks. + +Preferred solutions involve replacing `TaskCompletionSource` with another type that makes tracking the work involved automatic. +These include: + +1. Use `JoinableTaskFactory.RunAsync` and store the resulting `JoinableTask` in a field to await later. +1. Use `AsyncLazy` for one-time init work that should only start if required. Be sure to pass in a `JoinableTaskFactory` instance to its constructor. + +Assuming you must keep using `TaskCompletionSource` though, here's how it can be done as safely as possible. +Joining a set of unknown work is best done with the `JoinableTaskCollection` class. +It is the responsibility of `SomeClass` in the example above to work with this collection to avoid deadlocks, like this: + +```cs +class SomeClass +{ + TaskCompletionSource tcs = new(); + JoinableTaskCollection jtc; + JoinableTaskFactory jtf; + + internal SomeClass(JoinableTaskContext joinableTaskContext) + { + this.jtc = joinableTaskContext.CreateCollection(); + this.jtf = joinableTaskContext.CreateFactory(this.jtc); + } + + public async Task MyMethodAsync() + { + // Our caller is interested in completion of the TaskCompletionSource, + // so join the collected effort while waiting, to avoid deadlocks. + using (this.jtc.Join()) + { + await this.tcs.Task; /* This analyzer will report warning on this line. */ + } + + /* do more stuff */ + } + + void UnlockProgress() + { + this.tcs.TrySetResult(true); + } + + async Task KeyMasterAsync() + { + // As this method must complete to signal the TaskCompletionSource, + // all of its work must be done within the context of a JoinableTask + // that belongs to the JoinableTaskCollection. + // jtf.RunAsync will add the JoinableTask it creates to the jtc collection + // because jtf was created with jtc as an argument in our constructor. + await this.jtf.RunAsync(async delegate + { + // Because we're in the jtc collection, anyone waiting on MyMethodAsync + // will automatically lend us use of the main thread if they have it + // to avoid deadlocks. + // It does NOT matter whether we use jtf or another JoinableTaskFactory instance + // at this point. + await anyOldJTF.SwitchToMainThreadAsync(); + + // do some work + // Unblock others + this.UnlockProgress(); + }); + } +} +``` + +Notice how the public API of the class does not need to expose any `JoinableTask`-related types. +It's an implementation detail of the class. + +This works fine when the class itself fully controls the work to complete the `TaskCompletionSource`. +When _other_ classes also do work (independently of work started within `SomeClass`), the placement and access to the `JoinableTaskFactory` that is associated with the `JoinableTaskCollection` may need to be elevated so that other classes can access it as well so that *all* the work required to complete the `TaskCompletionSource` will be tracked. + +### Task chaining or other means to ensure sequential execution + +Task chaining is another technique that can lead to deadlocks. +Task chaining is where a single `Task` is kept in a field and used to call `Task.ContinueWith` to append another Task, and the resulting Task is then assigned to the field, like this: + +```cs +class TaskChainingExample +{ + private readonly object lockObject = new(); + private Task lastTask = Task.CompletedTask; + + internal Task AddWorkToEnd(Funk work) + { + lock (this.lockObject) + { + return this.lastTask = this.lastTask.ContinueWith(_ => work()).Unwrap(); + } + } +} +``` + +(Note: The above example has several *other* issues that would require more code to address, but it illustrates the idea of task chaining.) + +The deadlock risk with task chaining is that again, the chain of tasks come together to form a kind of private queue which the `JoinableTaskFactory` has no visibility into. +When a task is not at the front of the queue but its owner blocks the main thread for its completion, and if any other task ahead of it in the queue needs the main thread, a deadlock will result. + +For this reason (and several others), task chaining is *not* recommended. +Instead, you can achieve a thread-safe queue that executes work sequentially by utilizing the `ReentrantSemaphore` class. + +Fixing the above example would translate to this (allowing for a variety of reentrancy modes): + +```cs +class SequentialExecutingQueueExample +{ + private readonly ReentrantSemaphore semaphore = ReentrantSemaphore.Create(initialCount: 1, joinableTaskContext, ReentrancyMode.Stack); + + internal Task AddWorkToEnd(Func work) + { + return semaphore.ExecuteAsync(work); + } +} +``` diff --git a/docfx/analyzers/VSTHRD004.md b/docfx/analyzers/VSTHRD004.md new file mode 100644 index 000000000..39d3bbdf6 --- /dev/null +++ b/docfx/analyzers/VSTHRD004.md @@ -0,0 +1,43 @@ +# VSTHRD004 Await SwitchToMainThreadAsync + +Calls to `JoinableTaskFactory.SwitchToMainThreadAsync` must be awaited +or it is a no-op. + +## Examples of patterns that are flagged by this analyzer + +```csharp +void MyMethod() +{ + joinableTaskFactory.SwitchToMainThreadAsync(); + UIThreadBoundWork(); +} +``` + +## Solution + +Add `await` in front of the call to `JoinableTaskFactory.SwitchToMainThreadAsync`. + +This requires an async context. Here, we fix the problem by making the outer method async: + +```csharp +async Task MyMethodAsync() +{ + await joinableTaskFactory.SwitchToMainThreadAsync(); + UIThreadBoundWork(); +} +``` + + +Alternatively if found in a synchronous method that cannot be made async, +this failure can be fixed by lifting the code into a delegate passed to `JoinableTaskFactory.Run`: + +```csharp +void MyMethod() +{ + joinableTaskFactory.Run(async delegate + { + await joinableTaskFactory.SwitchToMainThreadAsync(); + UIThreadBoundWork(); + }); +} +``` diff --git a/docfx/analyzers/VSTHRD010.md b/docfx/analyzers/VSTHRD010.md new file mode 100644 index 000000000..bfb01c11d --- /dev/null +++ b/docfx/analyzers/VSTHRD010.md @@ -0,0 +1,61 @@ +# VSTHRD010 Invoke single-threaded types on Main thread + +Acquiring, casting, or invoking single-threaded objects should be done after ensuring +that your code is running on the main thread. + +This analyzer can be configured to: +1. Recognize the objects that are single-threaded that are unique to your app or library. +2. Recognize synchronous methods that verify the caller is already on the main thread. +3. Recognize methods that switch to the main thread when the caller awaits them. + Calls to `JoinableTaskFactory.SwitchToMainThreadAsync` methods are pre-configured. + +See our [configuration](configuration.md) topic to learn more about customizing this analyzer. + +This analyzer also recognizes requirements to use the main thread transitively within your solution. +For example, if method `A()` invokes a type that we know from configuration requires the main thread, +and `B()` calls `A()`, then the `B` method also needs the UI thread transitively. +This analyzer flags `B()` as needing to call a method that throws if not already on the main thread +only when `A()` is written to call such a method. + +**NOTE:** This analyzer requires [full solution analysis](fsa.md). + +## Examples of patterns that are flagged by this analyzer + +This example is based on the configuration available from the Visual Studio SDK +that defines `IVs*` interfaces as requiring the main thread. + +```csharp +private void CallVS() +{ + IVsSolution sln = GetIVsSolution(); + sln.SetProperty(); // This analyzer will report warning on this invocation. +} +``` + +## Solution + +First ensure you are running on the main thread before interacting with single-threaded objects. +Either throw when you are not on the appropriate thread, or explicitly switch to the +main thread. + +This solution example is based on the configuration available from the Visual Studio SDK +that defines `ThreadHelper.ThrowIfNotOnUIThread()` as one which throws if the caller +is not already on the main thread. + +```csharp +private void CallVS() +{ + ThreadHelper.ThrowIfNotOnUIThread(); + IVsSolution sln = GetIVsSolution(); + sln.SetProperty(); // This analyzer will not report warning on this invocation. +} + +private async Task CallVSAsync() +{ + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + IVsSolution sln = GetIVsSolution(); + sln.SetProperty(); // This analyzer will not report warning on this invocation. +} +``` + +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/docfx/analyzers/VSTHRD011.md b/docfx/analyzers/VSTHRD011.md new file mode 100644 index 000000000..40b7e94fe --- /dev/null +++ b/docfx/analyzers/VSTHRD011.md @@ -0,0 +1,57 @@ +# VSTHRD011 Use `AsyncLazy` + +The `Lazy` type executes the value factory just once and +the value factory inherits the context of the first one to request the +`Lazy.Value` property's value. This can lead to deadlocks when +the value factory attempts to switch to the main thread. + +## Examples of patterns that are flagged by this analyzer + +### Using `Lazy` where `T` is `Task` + +When `T` is `Task` (because the value factory is an async method), +if the first caller had no access to the main thread, and the value factory +requires it, it will block. If later a second caller calls the `Value` property +and that second caller is blocking the UI thread for its result, it will deadlock. + +```csharp +var lazy = new Lazy>(async delegate // analyzer flags this line +{ + await Task.Yield(); + return 3; +}); + +int value = await lazy.Value; +``` + +### Using synchronously blocking methods in `Lazy` value factories + +When the value factory passed to the `Lazy` constructor calls synchronously +blocking methods such as `JoinableTaskFactory.Run`, only the first caller +can help any required transition to the main thread. + +```csharp +var lazy = new Lazy(delegate +{ + return joinableTaskFactory.Run(async delegate { // analyzer flags this line + int result = await SomeAsyncMethod(); + return result + 3; + }); +}); + +int value = lazy.Value; +``` + +## Solution + +Use `AsyncLazy` with an async value factory: + +```csharp +var lazy = new AsyncLazy(async delegate +{ + await Task.Yield(); + return 3; +}); + +int value = await lazy.GetValueAsync(); +``` diff --git a/docfx/analyzers/VSTHRD012.md b/docfx/analyzers/VSTHRD012.md new file mode 100644 index 000000000..31710a1cb --- /dev/null +++ b/docfx/analyzers/VSTHRD012.md @@ -0,0 +1,33 @@ +# VSTHRD012 Provide `JoinableTaskFactory` where allowed + +When constructing types or calling methods that accept a `JoinableTaskFactory` +or `JoinableTaskContext`, take the opportunity to supply one if your application +has a main thread with a single threaded `SynchronizationContext` such as WPF or WinForms. + +## Examples of patterns that are flagged by this analyzer + +```csharp +void F() { + var o = new AsyncLazy(() => Task.FromResult(1)); // analyzer flags this line +} +``` + +## Solution + +Call the overload that accepts a `JoinableTaskFactory` or `JoinableTaskContext` instance: + +```csharp +void F() { + var o = new AsyncLazy(() => Task.FromResult(1), this.JoinableTaskFactory); +} +``` + +## Suppression + +You can suppress the diagnostic by explicitly specifying `null` for the argument: + +```csharp +void F() { + var o = new AsyncLazy(() => Task.FromResult(1), null); +} +``` diff --git a/docfx/analyzers/VSTHRD100.md b/docfx/analyzers/VSTHRD100.md new file mode 100644 index 000000000..b1a7278d5 --- /dev/null +++ b/docfx/analyzers/VSTHRD100.md @@ -0,0 +1,56 @@ +# VSTHRD100 Avoid `async void` methods + +Methods with `async void` signatures make it impossible for their caller to track +the entire asynchronous operation and handle exceptions that may be thrown by that method. +If the method throws an exception, it crashes the process. + +## Examples of patterns that are flagged by this analyzer + +```csharp +async void DoSomethingAsync() +{ + await SomethingElseAsync(); +} +``` + +## Solution + +Change the method to return `Task` instead of `void`. + +```csharp +async Task DoSomethingAsync() +{ + await SomethingElseAsync(); +} +``` + +A code fix is offered that automatically changes the return type of the method. + +### Event handlers + +For event handlers, avoid `async void` by using `RunAsync`: +```csharp +obj.Event += (s, e) => joinableTaskFactory.RunAsync(() => OnEventAsync(s, e)); +} + +private async Task OnEventAsync(object sender, EventArgs e) +{ + // async code here. +} +``` + +When using method group syntax as an argument, you can define the method with the required signature, without the `async` modifier, and define an anonymous delegate or lambda within the method, like this: + +```cs +var menuItem = new MenuCommand(HandleEvent, commandId); + +private void HandleEvent(object sender, EventArgs e) +{ + _ = joinableTaskFactory.RunAsync(async () => + { + // async code + }); +} +``` + +Refer to [Async/Await - Best Practices in Asynchronous Programming](https://msdn.microsoft.com/en-us/magazine/jj991977.aspx) for more info. diff --git a/docfx/analyzers/VSTHRD101.md b/docfx/analyzers/VSTHRD101.md new file mode 100644 index 000000000..10f9e411d --- /dev/null +++ b/docfx/analyzers/VSTHRD101.md @@ -0,0 +1,71 @@ +# VSTHRD101 Avoid unsupported async delegates + +C# allows you to define async delegates or lambdas and use them in contexts that accept +void-returning delegates, thus creating an `async void` method such as is forbidden by +[VSTHRD100](VSTHRD100.md), but is much harder to catch when simply looking at the code +because for the same syntax, the C# compiler will create an `async Func` delegate +or an `async void` delegate based on the type expected by the method being invoked. + +This analyzer helps prevent inadvertent creation of `async void` delegates. + +## Examples of patterns that are flagged by this analyzer + +```csharp +void StartWatching(ObservableCollection oc) +{ + // This delegate becomes an "async void" method to match the EventHandler delegate type. + oc.CollectionChanged += async () => + { + await Task.Yield(); + }; +} + +void StartWatching(ObservableCollection oc) +{ + // This delegate becomes an "async void" method to match the Action delegate type. + Callback(async () => + { + await Task.Yield(); + }); +} + +void Callback(Action action) +{ + // out of scope of sample +} +``` + +## Solution + +1. Wrap the asynchronous behavior in another method that accepts a `Func` delegate. +1. Change the receiving method's expected delegate type to one that returns a `Task` or `Task`. +1. Implement the delegate synchronously. + +```csharp +void StartWatching(ObservableCollection oc) +{ + oc.CollectionChanged += () => + { + // The outer delegate is synchronous, but kicks off async work via a method that accepts an async delegate. + joinableTaskFactory.RunAsync(async delegate { + await Task.Yield(); + }); + }; +} + +void StartWatching(ObservableCollection oc) +{ + // This delegate becomes an "async Task" method to match the Func delegate type. + Callback(async () => + { + await Task.Yield(); + }); +} + +void Callback(Func action) +{ + // out of scope of sample +} +``` + +Refer to [Async/Await - Best Practices in Asynchronous Programming](https://msdn.microsoft.com/en-us/magazine/jj991977.aspx) for more info. diff --git a/docfx/analyzers/VSTHRD102.md b/docfx/analyzers/VSTHRD102.md new file mode 100644 index 000000000..60ba1cb71 --- /dev/null +++ b/docfx/analyzers/VSTHRD102.md @@ -0,0 +1,56 @@ +# VSTHRD102 Implement internal logic asynchronously + +Internal or private methods may be invoked by public methods that are asynchronous. +If the internal method has an opportunity to do work asynchronously, it should do so +in order that async public members can truly be async. + +## Examples of patterns that are flagged by this analyzer + +```csharp +public void PublicMethod() +{ + DoWork(); +} + +public async Task PublicMethodAsync() +{ + DoWork(); + await Task.Yield(); +} + +internal void DoWork() +{ + joinableTaskFactory.Run(async delegate // Analyzer will flag this line + { + await DoSomethingAsync(); + }); +} +``` + +Note how `DoWork()` synchronously blocks for both `PublicMethod()` and `PublicMethodAsync()`. + +## Solution + +Remove the synchronously blocking behavior and make the method async. + +```csharp +public void PublicMethod() +{ + joinableTaskFactory.Run(() => PublicMethodAsync()); +} + +public async Task PublicMethodAsync() +{ + await DoWorkAsync(); + await Task.Yield(); +} + +internal async Task DoWorkAsync() +{ + await DoSomethingAsync(); +} +``` + +Note how `DoWorkAsync()` now allows `PublicMethodAsync()` to do its work asynchronously +while `PublicMethod()` continues to synchronously block, giving your external caller the option +as to whether to do work asynchronously or synchronously. diff --git a/docfx/analyzers/VSTHRD103.md b/docfx/analyzers/VSTHRD103.md new file mode 100644 index 000000000..348032057 --- /dev/null +++ b/docfx/analyzers/VSTHRD103.md @@ -0,0 +1,29 @@ +# VSTHRD103 Call async methods when in an async method + +In a method which is already asynchronous, calls to other methods should +be to their async versions, where they exist. + +## Examples of patterns that are flagged by this analyzer + +```csharp +Task DoAsync() +{ + file.Read(buffer, 0, 10); +} +``` + +All methods where an Async-suffixed equivalent exists will produce this warning +when called from a `Task`-returning method. +In addition, calling `Task.Wait()`, `Task.Result` or `Task.GetAwaiter().GetResult()` +will produce this warning. + +## Solution + +Await the async version of the method: + +```csharp +async Task DoAsync() +{ + await file.ReadAsync(buffer, 0, 10); +} +``` diff --git a/docfx/analyzers/VSTHRD104.md b/docfx/analyzers/VSTHRD104.md new file mode 100644 index 000000000..aabbe5722 --- /dev/null +++ b/docfx/analyzers/VSTHRD104.md @@ -0,0 +1,36 @@ +# VSTHRD104 Offer async option + +When a publicly accessible method uses `JoinableTaskFactory.Run`, there should be +another way to access the async behavior without synchronously blocking the thread +so that an async caller can be async throughout. + +This rule encourages this pattern by recognizing when some method *Foo* exists and +calls `JoinableTaskFactory.Run` that there is also a method *FooAsync*. +The recommended pattern then is for *Foo* to call *FooAsync* from the delegate +passed to `JoinableTaskFactory.Run` so that the implementation only need be written once. + +## Examples of patterns that are flagged by this analyzer + +```csharp +public void Foo() { + this.joinableTaskFactory.Run(async delegate { + await Task.Yield(); + }); +} +``` + +## Solution + +Add a FooAsync method, and (optionally) call it from the Foo method: + +```csharp +public void Foo() { + this.joinableTaskFactory.Run(async delegate { + await FooAsync(); + }); +} + +public async Task FooAsync() { + await Task.Yield(); +} +``` diff --git a/docfx/analyzers/VSTHRD105.md b/docfx/analyzers/VSTHRD105.md new file mode 100644 index 000000000..06c3e34a4 --- /dev/null +++ b/docfx/analyzers/VSTHRD105.md @@ -0,0 +1,83 @@ +# VSTHRD105 Avoid method overloads that assume `TaskScheduler.Current` + +Certain methods in the .NET Framework have overloads that allow specifying or omitting +a `TaskScheduler` instance. Always specify one explicitly to avoid the assumed `TaskScheduler.Current` +value, whose behavior is defined by your caller and may vary at runtime. + +The "current" `TaskScheduler` is defined by the one that is executing the currently running code. +But when your code is executing without having been scheduled by a `TaskScheduler` (as is the case with most code), +then the `TaskScheduler.Current` property returns `TaskScheduler.Default` which schedules tasks on the thread pool. +This leads many to incorrectly assume that task scheduling methods such as `StartNew` and `ContinueWith` default +to using the thread pool when in fact their default behavior varies by your caller. + +This variability in behavior leads to bugs when, for example, `TaskScheduler.Current` returns a `TaskScheduler` +that executes tasks on the application's main thread and/or only executes one task at once, such as one obtained +from the `TaskScheduler.FromCurrentSynchronizationContext()` method. +Such a circumstance often leads to deadlocks or responsiveness issues in the application. + +Always explicitly specifying `TaskScheduler.Default` (or other if appropriate) ensures your code will schedule +tasks in a predictable, consistent way. + +No diagnostic is produced by this analyzer when `TaskFactory.StartNew` is invoked on a private instance +of `TaskFactory`, since it may in fact have a safe default for `TaskScheduler`. + +Similar rules: [CA2008 (DoNotCreateTasksWithoutPassingATaskSchedulerAnalyzer)](https://github.com/dotnet/roslyn-analyzers/blob/32d8f1e397439035f0ecb5f61a9e672225f0ecdb/src/Microsoft.NetCore.Analyzers/Core/Tasks/DoNotCreateTasksWithoutPassingATaskScheduler.cs) + +## Examples of patterns that are flagged by this analyzer + +```csharp +private void FirstMethod() +{ + TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); + Task.Factory.StartNew( + () => + { + this.AnotherMethod(); + }, + System.Threading.CancellationToken.None, + TaskCreationOptions.None, + uiScheduler); +} + +private void AnotherMethod() +{ + // TaskScheduler.Current is assumed here, which is determined by our caller. + var nestedTask = Task.Factory.StartNew( // analyzer flags this line + () => + { + // Ooops, we're still on the UI thread when called by FirstMethod. + // But we might be on the thread pool if someone else called us. + }); +} +``` + +## Solution + +Specify a `TaskScheduler` explicitly to suppress the warning: + +```csharp +private void FirstMethod() +{ + TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); + Task.Factory.StartNew( + () => + { + this.AnotherMethod(); + }, + CancellationToken.None, + TaskCreationOptions.None, + uiScheduler); +} + +private void AnotherMethod() +{ + var nestedTask = Task.Factory.StartNew( + () => + { + // Ah, now we're reliably running on the thread pool. :) + }, + CancellationToken.None, + TaskCreationOptions.None, + TaskScheduler.Default); // Specify TaskScheduler explicitly here. +} +``` diff --git a/docfx/analyzers/VSTHRD106.md b/docfx/analyzers/VSTHRD106.md new file mode 100644 index 000000000..f47fc46ce --- /dev/null +++ b/docfx/analyzers/VSTHRD106.md @@ -0,0 +1,31 @@ +# VSTHRD106 Use `InvokeAsync` to raise async events + +Asynchronous events (those typed as `AsyncEventHandler`) must be raised carefully to ensure +all event handlers are invoked and awaited on. + +Although C# lets you invoke event handlers naturally, it has no awareness of async event handlers +and thus will not let you correctly await on their invocation nor invoke them sequentially. + +## Examples of patterns that are flagged by this analyzer + +```csharp +public AsyncEventHandler Clicked; + +async Task OnClicked() { + await Clicked(this, EventArgs.Empty); // only awaits the first event handler. +} +``` + +## Solution + +Use the `InvokeAsync` extension method defined in the `TplExtensions` class and await its result. +This will ensure each event handler completes before invoking the next event handler in the list, +similar to the default behavior for raising synchronous events. + +```csharp +public AsyncEventHandler Clicked; + +async Task OnClicked() { + await Clicked.InvokeAsync(this, EventArgs.Empty); // await for the completion of all handlers. +} +``` diff --git a/docfx/analyzers/VSTHRD107.md b/docfx/analyzers/VSTHRD107.md new file mode 100644 index 000000000..8694cf2ea --- /dev/null +++ b/docfx/analyzers/VSTHRD107.md @@ -0,0 +1,27 @@ +# VSTHRD107 Await Task within using expression + +The C# `using` statement requires that the used expression implement `IDisposable`. +Because `Task` implements `IDisposable`, one may accidentally omit an `await` operator +and `Dispose` of the `Task` instead of the `T` result itself when `T` derives from `IDisposable`. + +## Examples of patterns that are flagged by this analyzer + +```csharp +AsyncSemaphore lck; +using (lck.EnterAsync()) +{ + // ... +} +``` + +## Solution + +Add the `await` operator within the `using` expression. + +```csharp +AsyncSemaphore lck; +using (await lck.EnterAsync()) +{ + // ... +} +``` diff --git a/docfx/analyzers/VSTHRD108.md b/docfx/analyzers/VSTHRD108.md new file mode 100644 index 000000000..c2b986e2a --- /dev/null +++ b/docfx/analyzers/VSTHRD108.md @@ -0,0 +1,46 @@ +# VSTHRD108 Assert thread affinity unconditionally + +When a method has thread affinity and throws if called from the wrong thread, it should do so without regard to any other condition. This helps ensure the caller will notice early during development that they are calling from the wrong thread. Extra conditions can hide the problem till end users discover an application failure. + +## Examples of patterns that are flagged by this analyzer + +```csharp +private int? age; + +public int GetAge() +{ + if (!this.age.HasValue) + { + ThreadHelper.ThrowIfNotOnUIThread(); + this.age = DoExpensiveUIThreadWork(); + } + + return this.age.Value; +} +``` + +The problem here is that although the UI thread is only strictly required when the field is actually initialized, callers generally cannot predict whether they will be the first or a subsequent caller. If they call from a background thread and tend to be a subsequent caller, no exception will be thrown. But under some conditions in the app when they happen to be the first caller, they'll fail at runtime because they're calling from the background thread. + +## Solution + +Move the code that throws when not on the UI thread outside the conditional block. + +```csharp +private int? age; + +public int GetAge() +{ + ThreadHelper.ThrowIfNotOnUIThread(); + if (!this.age.HasValue) + { + this.age = DoExpensiveUIThreadWork(); + } + + return this.age.Value; +} +``` + +## Configuration + +This analyzer is configurable via the `vs-threading.MainThreadAssertingMethods.txt` file. +See our [configuration](configuration.md) topic for more information. diff --git a/docfx/analyzers/VSTHRD109.md b/docfx/analyzers/VSTHRD109.md new file mode 100644 index 000000000..7e28198b7 --- /dev/null +++ b/docfx/analyzers/VSTHRD109.md @@ -0,0 +1,29 @@ +# VSTHRD109 Switch instead of assert in async methods + +Methods that are or can be async should switch to the main thread when necessary +rather than throw an exception if invoked from a different thread. +This allows callers to invoke any async method from any thread +without having to concern themselves with the threading requirements of a method that +can support its own threading requirements by switching. + +## Examples of patterns that are flagged by this analyzer + +```csharp +async Task FooAsync() { + ThreadHelper.ThrowIfNotOnUIThread(); + DoStuff(); + await DoMoreStuff(); +} +``` + +## Solution + +Use `await SwitchToMainThreadAsync()` instead: + +```csharp +async Task FooAsync() { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + DoStuff(); + await DoMoreStuff(); +} +``` diff --git a/docfx/analyzers/VSTHRD110.md b/docfx/analyzers/VSTHRD110.md new file mode 100644 index 000000000..6e7bc44af --- /dev/null +++ b/docfx/analyzers/VSTHRD110.md @@ -0,0 +1,75 @@ +# VSTHRD110 Observe result of async calls + +Tasks returned from async methods should be awaited, or assigned to a variable for observation later. +Methods that return `Task`s often complete and report their work via the `Task` they return, and simply +invoking the method does not guarantee that its work is complete nor successful. Using the `await` keyword +just before the method call causes execution of the calling method to effectively suspend until the called +method has completed and rethrows any exception thrown by the method. + +When a `Task` or `Task` is returned and is not awaited or redirected in some other way, +within the context of a synchronous method, a warning is reported. + +This rule does *not* apply to calls made within async methods, since [CS4014][CS4014] already reports these. + +## Examples of patterns that are flagged by this analyzer + +```csharp +void Foo() { + DoStuffAsync(); +} + +async Task DoStuffAsync() { /* ... */ } +``` + +## Solution + +Convert the method to be async and await the expression: + +```csharp +async Task FooAsync() { + await DoStuffAsync(); +} + +async Task DoStuffAsync() { /* ... */ } +``` + +When the calling method's signature cannot be changed, wrap the method body in a `JoinableTaskFactory.Run` delegate instead: + +```csharp +void Foo() { + jtf.Run(async delegate { + await DoStuffAsync(); + }); +} + +async Task DoStuffAsync() { /* ... */ } +``` + +One other option is to assign the result of the method call to a field or local variable, presumably to track it later: + +```csharp +void Foo() { + Task watchThis = DoStuffAsync(); +} + +async Task DoStuffAsync() { /* ... */ } +``` + +When tracking the `Task` with a field, remember that to await it later without risk of deadlocking, +wrap it in a `JoinableTask` using `JoinableTaskFactory.RunAsync`, per [the 3rd rule](../docs/threading_rules.md#Rule3). + +```csharp +JoinableTask watchThis; + +void Foo() { + this.watchThis = jtf.RunAsync(() => DoStuffAsync()); +} + +async Task WaitForFooToFinishAsync() { + await this.watchThis; +} + +async Task DoStuffAsync() { /* ... */ } +``` + +[CS4014]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs4014 diff --git a/docfx/analyzers/VSTHRD111.md b/docfx/analyzers/VSTHRD111.md new file mode 100644 index 000000000..0495e84f0 --- /dev/null +++ b/docfx/analyzers/VSTHRD111.md @@ -0,0 +1,38 @@ +# VSTHRD111 Use `.ConfigureAwait(bool)` + +Some code bases, particularly libraries with no affinity to an app's UI thread, are advised to use `.ConfigureAwait(false)` for each and every _await_ because it can avoid deadlocks after those calls start on an application's UI thread and the app later decides to synchronously block the UI thread waiting for those tasks to finish. Using `.ConfigureAwait(false)` also allows continuations to switch to a background thread even when no synchronous blocking would cause a deadlock, which makes for a more responsive application and possibly higher throughput of async operations. + +Note that this scenario can also be solved using the `JoinableTaskFactory`, but many class libraries may not wish to depend on the application proffers an instance of that type to the library. Where JoinableTaskFactory _does_ apply, use of `.ConfigureAwait(false)` is _not_ recommended. See [this topic](https://github.com/Microsoft/vs-threading/blob/main/doc/cookbook_vs.md#should-i-await-a-task-with-configureawaitfalse) for more on when `.ConfigureAwait(false)` and `.ConfigureAwait(true)` are appropriate. + +**This analyzer's diagnostics are *hidden* by default**. You should enable the rule for libraries that use to require this await suffix. + +## Examples of patterns that are flagged by this analyzer + +Any await on `Task` or `ValueTask` without the `.ConfigureAwait(bool)` method called on it will be flagged. + +```csharp +async Task FooAsync() { + await DoStuffAsync(); // This line is flagged + await DoMoreStuffAsync(); // This line is flagged +} + +async Task DoStuffAsync() { /* ... */ } +async ValueTask DoMoreStuffAsync() { /* ... */ } +``` + +## Solution + +Add `.ConfigureAwait(false)` or `.ConfigureAwait(true)` to the awaited `Task` or `ValueTask`. + +```csharp +async Task FooAsync() { + await DoStuffAsync().ConfigureAwait(true); + await DoMoreStuffAsync().ConfigureAwait(false); +} + +async Task DoStuffAsync() { /* ... */ } +async ValueTask DoMoreStuffAsync() { /* ... */ } +``` + +Code fixes are offered for for this diagnostic to add either `.ConfigureAwait(false)` or `.ConfigureAwait(true)` +to an awaited expression. diff --git a/docfx/analyzers/VSTHRD112.md b/docfx/analyzers/VSTHRD112.md new file mode 100644 index 000000000..b9e3ae45c --- /dev/null +++ b/docfx/analyzers/VSTHRD112.md @@ -0,0 +1,66 @@ +# VSTHRD112 Implement `System.IAsyncDisposable` + +The `Microsoft.VisualStudio.Threading.IAsyncDisposable` interface is obsolete now that the +`System.IAsyncDisposable` interface has been defined for .NET Standard 2.0 and .NET Framework 4.6.1 +by the [`Microsoft.Bcl.AsyncInterfaces` NuGet package](https://www.nuget.org/packages/Microsoft.Bcl.AsyncInterfaces). + +New classes looking to support async disposable should use `System.IAsyncDisposable` instead of `Microsoft.VisualStudio.Threading.IAsyncDisposable`. +Existing classes that already implement `Microsoft.VisualStudio.Threading.IAsyncDisposable` should *also* implement `System.IAsyncDisposable` so the async disposal option will be recognized by code that only checks for presence of the new interface. + +## Examples of patterns that are flagged by this analyzer + +This class only implements `Microsoft.VisualStudio.Threading.IAsyncDisposable` and will produce the VSTHRD112 diagnostic: + +```cs +using Microsoft.VisualStudio.Threading; + +class SomeClass : IAsyncDisposable +{ + public Task DisposeAsync() + { + } +} +``` + +## Solution + +Implement `System.IAsyncDisposable` in addition to (or instead of) `Microsoft.VisualStudio.Threading.IAsyncDisposable`. +Add a package reference to `Microsoft.Bcl.AsyncInterfaces` if the compiler cannot find `System.IAsyncDisposable`. + +In this example, only `System.IAsyncDisposable` is supported, which is acceptable: + +```cs +using System; + +class SomeClass : IAsyncDisposable +{ + public ValueTask DisposeAsync() + { + } +} +``` + +In this next example, both interfaces are supported: + +```cs +class SomeClass : System.IAsyncDisposable, Microsoft.VisualStudio.Threading.IAsyncDisposable +{ + Task Microsoft.VisualStudio.Threading.IAsyncDisposable.DisposeAsync() + { + // Simply forward the call to the other DisposeAsync overload. + System.IAsyncDisposable self = this; + return self.DisposeAsync().AsTask(); + } + + ValueTask System.IAsyncDisposable.DisposeAsync() + { + // Interesting dispose logic here. + } +} +``` + +In the above example both `DisposeAsync` methods are explicit interface implementations. +Promoting one of the methods to be `public` is typically advised. +If one of these methods was already public and the class itself is public or protected, keep the same method public to avoid an API binary breaking change. + +An automated code fix may be offered for VSTHRD112 diagnostics. diff --git a/docfx/analyzers/VSTHRD113.md b/docfx/analyzers/VSTHRD113.md new file mode 100644 index 000000000..8d4dee677 --- /dev/null +++ b/docfx/analyzers/VSTHRD113.md @@ -0,0 +1,37 @@ +# VSTHRD113 Check for `System.IAsyncDisposable` + +The `Microsoft.VisualStudio.Threading.IAsyncDisposable` interface is obsolete now that the +`System.IAsyncDisposable` interface has been defined for .NET Standard 2.0 and .NET Framework 4.6.1 +by the [`Microsoft.Bcl.AsyncInterfaces` NuGet package](https://www.nuget.org/packages/Microsoft.Bcl.AsyncInterfaces). + +Existing code that tests for the `Microsoft.VisualStudio.Threading.IAsyncDisposable` interface on some object should also check for `System.IAsyncDisposable` and behave similarly in either case. +New code should consider only supporting the new `System.IAsyncDisposable` interface. + +## Examples of patterns that are flagged by this analyzer + +The following code only checks for the obsolete interface and is flagged by this diagnostic: + +```cs +using Microsoft.VisualStudio.Threading; + +if (obj is IAsyncDisposable asyncDisposable) +{ + await asyncDisposable.DisposeAsync(); +} +``` + +## Solution + +Fix this by adding a code branch for the new interface that behaves similarly +within the same containing code block: + +```cs +if (obj is Microsoft.VisualStudio.Threading.IAsyncDisposable vsThreadingAsyncDisposable) +{ + await vsThreadingAsyncDisposable.DisposeAsync(); +} +else if (obj is System.IAsyncDisposable bclAsyncDisposable) +{ + await bclAsyncDisposable.DisposeAsync(); +} +``` diff --git a/docfx/analyzers/VSTHRD114.md b/docfx/analyzers/VSTHRD114.md new file mode 100644 index 000000000..c20c3fb53 --- /dev/null +++ b/docfx/analyzers/VSTHRD114.md @@ -0,0 +1,31 @@ +# VSTHRD114 Avoid returning a null Task + +Returning `null` from a non-async `Task`/`Task` method will cause a `NullReferenceException` at runtime. This problem can be avoided by returning `Task.CompletedTask`, `Task.FromResult(null)` or `Task.FromResult(default(T))` instead. + +## Examples of patterns that are flagged by this analyzer + +Any non-async `Task` returning method with an explicit `return null;` will be flagged. + +```csharp +Task DoAsync() { + return null; +} + +Task GetSomethingAsync() { + return null; +} +``` + +## Solution + +Return a task like `Task.CompletedTask` or `Task.FromResult`. + +```csharp +Task DoAsync() { + return Task.CompletedTask; +} + +Task GetSomethingAsync() { + return Task.FromResult(null); +} +``` diff --git a/docfx/analyzers/VSTHRD115.md b/docfx/analyzers/VSTHRD115.md new file mode 100644 index 000000000..5e1560911 --- /dev/null +++ b/docfx/analyzers/VSTHRD115.md @@ -0,0 +1,38 @@ +# VSTHRD115 Avoid creating a JoinableTaskContext with an explicit `null` `SynchronizationContext` + +Constructing a `JoinableTaskContext` with an explicit `null` `SynchronizationContext` is not recommended as a means to construct an instance for use in unit tests or processes without a main thread. +This is because the constructor will automatically use `SynchronizationContext.Current` in lieu of a non-`null` argument. +If `SynchronizationContext.Current` happens to be non-`null`, the constructor may unexpectedly configure the new instance as if a main thread were present. + +## Examples of patterns that are flagged by this analyzer + +```csharp +void SetupJTC() { + this.jtc = new JoinableTaskContext(null, null); +} +``` + +This code *appears* to configure the `JoinableTaskContext` to not be associated with any `SynchronizationContext`. +But in fact it will be associated with the current `SynchronizationContext` if one is present. + +## Solution + +If you intended to inherit `SynchronizationContext.Current` to initialize with a main thread, +provide that value explicitly as the second argument to suppress the warning: + +```cs +void SetupJTC() { + this.jtc = new JoinableTaskContext(null, SynchronizationContext.Current); +} +``` + +If you intended to create a `JoinableTaskContext` for use in a unit test or in a process without a main thread, +call `JoinableTaskContext.CreateNoOpContext()` instead: + +```cs +void SetupJTC() { + this.jtc = JoinableTaskContext.CreateNoOpContext(); +} +``` + +Code fixes are offered to update code to either of the above patterns. diff --git a/docfx/analyzers/VSTHRD200.md b/docfx/analyzers/VSTHRD200.md new file mode 100644 index 000000000..3f87cf273 --- /dev/null +++ b/docfx/analyzers/VSTHRD200.md @@ -0,0 +1,47 @@ +# VSTHRD200 Use `Async` suffix for async methods + +The .NET Guidelines for async methods includes that such methods +should have names that include an "Async" suffix. + +Methods that return awaitable types such as `Task` or `ValueTask` +should have an Async suffix. +Methods that do not return awaitable types should not use the Async suffix. + +## Examples of patterns that are flagged by this analyzer + +This `Task`-returning method should have a name that ends with Async: + +```csharp +async Task DoSomething() // analyzer flags this line +{ + await Task.Yield(); +} +``` + +This method should not have a name that ends with Async, since it does not return an awaitable type: + +```csharp +bool DoSomethingElseAsync() // analyzer flags this line +{ + return false; +} +``` + +## Solution + +Simply rename the method to end in "Async" (or remove the suffix, as appropriate): + +```csharp +async Task DoSomethingAsync() +{ + await Task.Yield(); +} + +bool DoSomethingElse() +{ + return false; +} +``` + + +A code fix exists to automatically rename such methods. diff --git a/doc/analyzers/configuration.md b/docfx/analyzers/configuration.md similarity index 100% rename from doc/analyzers/configuration.md rename to docfx/analyzers/configuration.md diff --git a/doc/analyzers/fsa.md b/docfx/analyzers/fsa.md similarity index 91% rename from doc/analyzers/fsa.md rename to docfx/analyzers/fsa.md index f1b49668d..48afeb68d 100644 --- a/doc/analyzers/fsa.md +++ b/docfx/analyzers/fsa.md @@ -6,4 +6,4 @@ Diagnostics reported by such an analyzer will appear in a full build log. They m To ensure the diagnostics are always visible, even when documents are open, select the "Enable full Solution Analysis" option, as shown below: -![Visual Studio Options -> Text Editor -> C# -> Advanced -> Enable full solution analysis option](images/fsa.png) \ No newline at end of file +![Visual Studio Options -> Text Editor -> C# -> Advanced -> Enable full solution analysis option](../images/fsa.png) diff --git a/doc/analyzers/index.md b/docfx/analyzers/index.md similarity index 81% rename from doc/analyzers/index.md rename to docfx/analyzers/index.md index 7b07b500b..4d2077c25 100644 --- a/doc/analyzers/index.md +++ b/docfx/analyzers/index.md @@ -6,23 +6,23 @@ Learn more about [how to install and configure these analyzers](installation.md) ID | Title | Severity | Supports | Default diagnostic severity ---- | --- | --- | --- | --- | -[VSTHRD001](VSTHRD001.md) | Avoid legacy thread switching methods | Critical | [1st rule](../threading_rules.md#Rule1) | 🔡 Warning -[VSTHRD002](VSTHRD002.md) | Avoid problematic synchronous waits | Critical | [2nd rule](../threading_rules.md#Rule2) | Warning -[VSTHRD003](VSTHRD003.md) | Avoid awaiting foreign Tasks | Critical | [3rd rule](../threading_rules.md#Rule3) | Warning -[VSTHRD004](VSTHRD004.md) | Await SwitchToMainThreadAsync | Critical | [1st rule](../threading_rules.md#Rule1) | Error -[VSTHRD010](VSTHRD010.md) | Invoke single-threaded types on Main thread | Critical | [1st rule](../threading_rules.md#Rule1) | Warning -[VSTHRD011](VSTHRD011.md) | Use `AsyncLazy` | Critical | [3rd rule](../threading_rules.md#Rule3) | Error -[VSTHRD012](VSTHRD012.md) | Provide JoinableTaskFactory where allowed | Critical | [All rules](../threading_rules.md) | Warning +[VSTHRD001](VSTHRD001.md) | Avoid legacy thread switching methods | Critical | [1st rule](../docs/threading_rules.md#Rule1) | 🔡 Warning +[VSTHRD002](VSTHRD002.md) | Avoid problematic synchronous waits | Critical | [2nd rule](../docs/threading_rules.md#Rule2) | Warning +[VSTHRD003](VSTHRD003.md) | Avoid awaiting foreign Tasks | Critical | [3rd rule](../docs/threading_rules.md#Rule3) | Warning +[VSTHRD004](VSTHRD004.md) | Await SwitchToMainThreadAsync | Critical | [1st rule](../docs/threading_rules.md#Rule1) | Error +[VSTHRD010](VSTHRD010.md) | Invoke single-threaded types on Main thread | Critical | [1st rule](../docs/threading_rules.md#Rule1) | Warning +[VSTHRD011](VSTHRD011.md) | Use `AsyncLazy` | Critical | [3rd rule](../docs/threading_rules.md#Rule3) | Error +[VSTHRD012](VSTHRD012.md) | Provide JoinableTaskFactory where allowed | Critical | [All rules](../docs/threading_rules.md) | Warning [VSTHRD100](VSTHRD100.md) | Avoid `async void` methods | Advisory | | Warning [VSTHRD101](VSTHRD101.md) | Avoid unsupported async delegates | Advisory | [VSTHRD100](VSTHRD100.md) | Warning -[VSTHRD102](VSTHRD102.md) | Implement internal logic asynchronously | Advisory | [2nd rule](../threading_rules.md#Rule2) | Info +[VSTHRD102](VSTHRD102.md) | Implement internal logic asynchronously | Advisory | [2nd rule](../docs/threading_rules.md#Rule2) | Info [VSTHRD103](VSTHRD103.md) | Call async methods when in an async method | Advisory | | Warning [VSTHRD104](VSTHRD104.md) | Offer async option | Advisory | | Info [VSTHRD105](VSTHRD105.md) | Avoid method overloads that assume `TaskScheduler.Current` | Advisory | | Warning [VSTHRD106](VSTHRD106.md) | Use `InvokeAsync` to raise async events | Advisory | | Warning [VSTHRD107](VSTHRD107.md) | Await Task within using expression | Advisory | | Error -[VSTHRD108](VSTHRD108.md) | Assert thread affinity unconditionally | Advisory | [1st rule](../threading_rules.md#Rule1), [VSTHRD010](VSTHRD010.md) | Warning -[VSTHRD109](VSTHRD109.md) | Switch instead of assert in async methods | Advisory | [1st rule](../threading_rules.md#Rule1) | Error +[VSTHRD108](VSTHRD108.md) | Assert thread affinity unconditionally | Advisory | [1st rule](../docs/threading_rules.md#Rule1), [VSTHRD010](VSTHRD010.md) | Warning +[VSTHRD109](VSTHRD109.md) | Switch instead of assert in async methods | Advisory | [1st rule](../docs/threading_rules.md#Rule1) | Error [VSTHRD110](VSTHRD110.md) | Observe result of async calls | Advisory | | Warning [VSTHRD111](VSTHRD111.md) | Use `.ConfigureAwait(bool)` | Advisory | | Hidden [VSTHRD112](VSTHRD112.md) | Implement `System.IAsyncDisposable` | Advisory | | Info diff --git a/doc/analyzers/installation.md b/docfx/analyzers/installation.md similarity index 96% rename from doc/analyzers/installation.md rename to docfx/analyzers/installation.md index a00c99297..9724a95b8 100644 --- a/doc/analyzers/installation.md +++ b/docfx/analyzers/installation.md @@ -26,7 +26,7 @@ You should generally use the latest version available, without regard to the ver There are [many rules](index.md) in the analyzer package. The default severity levels for the various rules are not appropriate for every type of project. -To get the best default severity levels for your project type, please review [these editorconfig recommendations](editorconfigs/README.md) and apply them to your project. +To get the best default severity levels for your project type, please review [these editorconfig recommendations](https://github.com/microsoft/vs-threading/blob/main/doc/editorconfigs/README.md) and apply them to your project. Some analyzers allow for [specialized configuration](configuration.md) that allows you to tailor them to your specific application or library to provide even more value to your team. diff --git a/docfx/analyzers/toc.yml b/docfx/analyzers/toc.yml new file mode 100644 index 000000000..cc7bee4ff --- /dev/null +++ b/docfx/analyzers/toc.yml @@ -0,0 +1,29 @@ +items: +- href: index.md +- href: configuration.md +- href: fsa.md +- href: installation.md +- href: VSTHRD001.md +- href: VSTHRD002.md +- href: VSTHRD003.md +- href: VSTHRD004.md +- href: VSTHRD010.md +- href: VSTHRD011.md +- href: VSTHRD012.md +- href: VSTHRD100.md +- href: VSTHRD101.md +- href: VSTHRD102.md +- href: VSTHRD103.md +- href: VSTHRD104.md +- href: VSTHRD105.md +- href: VSTHRD106.md +- href: VSTHRD107.md +- href: VSTHRD108.md +- href: VSTHRD109.md +- href: VSTHRD110.md +- href: VSTHRD111.md +- href: VSTHRD112.md +- href: VSTHRD113.md +- href: VSTHRD114.md +- href: VSTHRD115.md +- href: VSTHRD200.md diff --git a/doc/async_hang.md b/docfx/docs/async_hang.md similarity index 100% rename from doc/async_hang.md rename to docfx/docs/async_hang.md diff --git a/doc/cookbook_vs.md b/docfx/docs/cookbook_vs.md similarity index 100% rename from doc/cookbook_vs.md rename to docfx/docs/cookbook_vs.md diff --git a/doc/dumpasync.md b/docfx/docs/dumpasync.md similarity index 100% rename from doc/dumpasync.md rename to docfx/docs/dumpasync.md diff --git a/docfx/docs/features.md b/docfx/docs/features.md index eb3fbeb0d..0e517bcfb 100644 --- a/docfx/docs/features.md +++ b/docfx/docs/features.md @@ -1,3 +1,26 @@ # Features -TODO +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). + +* Async versions of many threading synchronization primitives + * `AsyncAutoResetEvent` + * `AsyncBarrier` + * `AsyncCountdownEvent` + * `AsyncManualResetEvent` + * `AsyncReaderWriterLock` + * `AsyncSemaphore` + * `ReentrantSemaphore` +* Async versions of very common types + * `AsyncEventHandler` + * `AsyncLazy` + * `AsyncLazyInitializer` + * `AsyncLocal` + * `AsyncQueue` +* Await extension methods + * Await on a `TaskScheduler` to switch to it. + Switch to a background thread with `await TaskScheduler.Default;` + * Await on a `Task` with a timeout + * Await on a `Task` with cancellation +* `JoinableTaskFactory` that allows you to schedule asynchronous or synchronous work + that does not deadlock with the UI thread even when the UI thread needs to + synchronously block on the result. diff --git a/docfx/docs/getting-started.md b/docfx/docs/getting-started.md index eff2c2fbc..e353bffba 100644 --- a/docfx/docs/getting-started.md +++ b/docfx/docs/getting-started.md @@ -5,8 +5,4 @@ Consume this library via its NuGet Package. Click on the badge to find its latest version and the instructions for consuming it that best apply to your project. -[![NuGet package](https://img.shields.io/nuget/v/Library.svg)](https://nuget.org/packages/Library) - -## Usage - -TODO +[![NuGet package](https://img.shields.io/nuget/v/Microsoft.VisualStudio.Threading.svg)](https://www.nuget.org/packages/Microsoft.VisualStudio.Threading) diff --git a/doc/library_with_jtf.md b/docfx/docs/library_with_jtf.md similarity index 98% rename from doc/library_with_jtf.md rename to docfx/docs/library_with_jtf.md index c93afe46d..29434ab75 100644 --- a/doc/library_with_jtf.md +++ b/docfx/docs/library_with_jtf.md @@ -124,4 +124,4 @@ Note that when you create your own default instance of JoinableTaskContext (i.e. If your library is app-agnostic (such that it cannot use an app-specific mechanism to obtain an instance of `JoinableTaskContext`) and has no good singleton class on which the app can set the `JoinableTaskContext` instance for the entire library's use, the last option is simply to take a `JoinableTaskContext` instance as a parameter when you need it. For example, [the `AsyncLazy` constructor accepts a `JoinableTaskFactory` as an optional parameter](https://github.com/Microsoft/vs-threading/blob/027bff027c829cab6be54dbd15551d763199ebf0/src/Microsoft.VisualStudio.Threading/AsyncLazy.cs#L60). -When you make the `JoinableTaskContext`/`JoinableTaskFactory` argument optional, the [VSTHRD012](analyzers/VSTHRD012.md) rule can guide your library's users to specify it if they have it available. +When you make the `JoinableTaskContext`/`JoinableTaskFactory` argument optional, the [VSTHRD012](../analyzers/VSTHRD012.md) rule can guide your library's users to specify it if they have it available. diff --git a/doc/testing_vs.md b/docfx/docs/testing_vs.md similarity index 100% rename from doc/testing_vs.md rename to docfx/docs/testing_vs.md diff --git a/doc/threading_rules.md b/docfx/docs/threading_rules.md similarity index 85% rename from doc/threading_rules.md rename to docfx/docs/threading_rules.md index f6de5c29a..cf6115457 100644 --- a/doc/threading_rules.md +++ b/docfx/docs/threading_rules.md @@ -1,7 +1,4 @@ -3 Threading Rules -================= - -## Background +# 3 Threading Rules In Visual Studio 2013, we consolidated all our lessons learned from writing a complex, multi-threaded component of Visual Studio into a small and simple set of @@ -15,7 +12,10 @@ extensions](cookbook_vs.md)). 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: +### Rule #1. Use `JTF.SwitchToMainThreadAsync` + +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 means of switching to the main thread is: @@ -34,7 +34,9 @@ The rules are listed below with minimal examples. For a more thorough explanatio 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: +### Rule #2. Use `JTF.Run` + +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 joinableTaskFactoryInstance.Run(async delegate @@ -43,7 +45,9 @@ joinableTaskFactoryInstance.Run(async delegate }); ``` -### Rule #3. If ever awaiting work that was started earlier, that work must be *joined*. +### Rule #3. Use `JTF.RunAsync` + +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: @@ -73,23 +77,25 @@ done immediately after kicking off an asynchronous operation. In particular, no method should call `Task.Wait()` or `Task.Result` on an incomplete `Task`. -### Additional "honorable mention" rules: (Not JTF related) +### Rule #4. Avoid `async void` + +(This one is more of an "honorable mention" rule, as it is not JTF related.) + +Never define `async void` methods. Make the methods return `Task` instead. -### Rule #4. Never define `async void` methods. Make the methods return `Task` instead. - - Exceptions thrown from `async void` methods always crash the process. - - Callers don't even have the option to `await` the result. - - 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 - `Func>` parameters. +- Exceptions thrown from `async void` methods always crash the process. +- Callers don't even have the option to `await` the result. +- 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 + `Func>` parameters. -Frequently Asked Questions ---------------- +## Frequently Asked Questions -##### Do I need to follow these rules? +### Do I need to follow these rules? All code that runs in Visual Studio itself should follow these rules. These rules have been reviewed by several senior and principal developers @@ -99,7 +105,7 @@ would do well to follow them in managed code where possible. Any other GUI app that invokes asynchronous code that it must occasionally block the UI thread on is also recommended to follow these rules. -##### Why should a method that has a dependency on a specific (kind of) thread be async? +### Why should a method that has a dependency on a specific (kind of) thread be async? Efficiency and responsiveness: Switching threads means that the original thread either can do something else productive (e.g., execute more work from @@ -115,7 +121,7 @@ requires, whether it's thread-safe, etc. The implementation can change over time to add or remove thread affinity, or to switch from locking to scheduling for thread safety, etc. -##### Why do I need to use `JoinableTaskFactory.Run` to synchronously block on asynchronous work rather than just calling `Task.Wait()` or `Task.Result`? +### Why do I need to use `JoinableTaskFactory.Run` to synchronously block on asynchronous work rather than just calling `Task.Wait()` or `Task.Result`? If you're on the main thread, because `Task.Wait` or `Task.Result` will often deadlock because you're now synchronously blocking the main thread for @@ -135,7 +141,7 @@ In contrast, when you use `JoinableTaskFactory.Run`, main thread deadlocks and threadpool exhaustion are automatically mitigated by reusing the blocking thread to execute the continuations. -##### Why not rely on COM marshaling to switch to the main thread when necessary? +### Why not rely on COM marshaling to switch to the main thread when necessary? There are several reasons for this: @@ -200,7 +206,7 @@ There are several reasons for this: 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? +### How do these rules protect me from re-entering random code on the main thread? By always using asynchronous mechanisms to marshal to the UI thread, you're effectively send a `PostMessage` to the UI thread, which will not re-enter @@ -218,7 +224,7 @@ main thread. That is, using this method to get to the UI thread just works: it avoids both deadlocks and undesirable reentrancy. The only time it deadlocks is when the threading rules listed above are not being followed. -##### Am I protected from other code re-entering my own code while it executes on the main thread? +### Am I protected from other code re-entering my own code while it executes on the main thread? Yes, somewhat. When you call `JoinableTaskFactory.Run` with an async delegate, when your delegate yields (using await) the message pump is temporarily @@ -236,7 +242,7 @@ disabling the message pump yourself, it's usually not a good idea because 3rd party code you may be calling could be relying on a functioning message pump. -##### I'm trying to analyze a hang around code that uses `JoinableTaskFactory`, but since transitions are asynchronous the active threads' call stacks don't tell the whole story. How can I find the cause and fix the hang? +### I'm trying to analyze a hang around code that uses `JoinableTaskFactory`, but since transitions are asynchronous the active threads' call stacks don't tell the whole story. How can I find the cause and fix the hang? Debugging async hangs in general is lacking debugger tooling support at the moment. The debugger and Windows teams are working to improve that @@ -250,11 +256,11 @@ attach WinDBG to the process and dump out incomplete async methods' states. This can be tedious, but we have a script in this file that you can use to make it much easier: [Async hang debugging][AsyncHangDebugging] -##### What is threadpool exhaustion, and why is it bad? +### What is threadpool exhaustion, and why is it bad? See our [threadpool starvation](threadpool_starvation.md) doc. -##### I'm writing an async method that isn't in a `JoinableTask`. Should I use `JTF.SwitchToMainThreadAsync()` to get to the UI thread? +### I'm writing an async method that isn't in a `JoinableTask`. Should I use `JTF.SwitchToMainThreadAsync()` to get to the UI thread? Yes. `JoinableTaskFactory.SwitchToMainThreadAsync()` works great outside a `JoinableTask`. It simply posts the continuation to the main thread for @@ -281,13 +287,13 @@ focus on the fact that at this point, your code needs the main thread and call `JTF.SwitchToMainThreadAsync()`. This allows your caller to set the 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? +### 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 `SynchronizationContext.Post`, which typically posts a message to the main thread, which puts it below RPC and above user input in priority. -[How to use a different priority for switching to the main thread in VS](cookbook_vs.md#how-to-switch-to-or-use-the-ui-thread-with-background-priority) +[How to use a different priority for switching to the main thread in VS](cookbook_vs.md#how-to-switch-to-or-use-the-ui-thread-with-a-specific-priority) The following describes how to replace the mechanism for getting to the UI thread in a host-independent way: diff --git a/doc/threadpool_starvation.md b/docfx/docs/threadpool_starvation.md similarity index 96% rename from doc/threadpool_starvation.md rename to docfx/docs/threadpool_starvation.md index 73222d14a..f8ac0db06 100644 --- a/doc/threadpool_starvation.md +++ b/docfx/docs/threadpool_starvation.md @@ -31,7 +31,7 @@ We use [PerfView](https://aka.ms/perfview) for these investigations. First, consider how we might come to suspect that thread pool starvation is to blame for a performance or responsiveness problem in the application. In PerfView if we were looking at a sluggish scenario with the CPU Stacks window, we might observe this: -![PerfView CPU Stacks view showing large columns of no CPU activity](images/cpu_stacks_showing_threadpool_starvation.png) +![PerfView CPU Stacks view showing large columns of no CPU activity](../images/cpu_stacks_showing_threadpool_starvation.png) Notice how the `When` column shows several vertical columns of time where there is little or no CPU activity. This is a good indication that we have either excessive lock contention or thread pool exhaustion. @@ -39,7 +39,7 @@ Notice how the `When` column shows several vertical columns of time where there Recent versions of Visual Studio raise an ETW event called `Microsoft-VisualStudio-Common/vs_core_perf_threadpoolstarvation` when thread pool starvation is detected. This is a sure clue of the problem and can give you a time range within the trace to focus your investigation. -![PerfView showing the VS ETW event that indicates thread pool starvation](images/vs_threadpoolstarvation_event.jpg) +![PerfView showing the VS ETW event that indicates thread pool starvation](../images/vs_threadpoolstarvation_event.jpg) ### Investigation steps @@ -49,7 +49,7 @@ While the CLR has thread pool ETW events to indicate thread starvation, these ev 1. In the Thread Time Stacks window, set the Start and End fields to the time range where you had a responsiveness problem. 1. Make sure symbols for the `clr` module are loaded. 1. In the "By Name" tab, find the `clr!ThreadpoolMgr::ExecuteWorkRequest` frame and invoke the "Include Items" command. This will add the frame to the `IncPats` field and filter all frames and stacks to those found on threadpool threads. -1. Also in the "By Name" tab, find the `BLOCKED_TIME` row and invoke the "Show Callers" command. ![PerfView By Name tab showing BLOCKED_TIME](images/blocked_time.png) This will show all stacks that led to any thread pool thread waiting instead of executing on the CPU. ![PerfView Callers of BLOCKED_TIME](images/blocked_time_callers.png) +1. Also in the "By Name" tab, find the `BLOCKED_TIME` row and invoke the "Show Callers" command. ![PerfView By Name tab showing BLOCKED_TIME](../images/blocked_time.png) This will show all stacks that led to any thread pool thread waiting instead of executing on the CPU. ![PerfView Callers of BLOCKED_TIME](../images/blocked_time_callers.png) Take a look at the stacks where the most threads or the most time is spent blocked. This is the code where you should focus your effort to remove the synchronous block. Common mitigations include: diff --git a/docfx/docs/toc.yml b/docfx/docs/toc.yml index 518ffadd9..0e6eb0e07 100644 --- a/docfx/docs/toc.yml +++ b/docfx/docs/toc.yml @@ -1,5 +1,10 @@ items: -- name: Features - href: features.md -- name: Getting Started - href: getting-started.md +- href: getting-started.md +- href: features.md +- href: threading_rules.md +- href: cookbook_vs.md +- href: testing_vs.md +- href: library_with_jtf.md +- href: threadpool_starvation.md +- href: async_hang.md +- href: dumpasync.md diff --git a/doc/images/blocked_time.png b/docfx/images/blocked_time.png similarity index 100% rename from doc/images/blocked_time.png rename to docfx/images/blocked_time.png diff --git a/doc/images/blocked_time_callers.png b/docfx/images/blocked_time_callers.png similarity index 100% rename from doc/images/blocked_time_callers.png rename to docfx/images/blocked_time_callers.png diff --git a/doc/images/cpu_stacks_showing_threadpool_starvation.png b/docfx/images/cpu_stacks_showing_threadpool_starvation.png similarity index 100% rename from doc/images/cpu_stacks_showing_threadpool_starvation.png rename to docfx/images/cpu_stacks_showing_threadpool_starvation.png diff --git a/doc/analyzers/images/fsa.png b/docfx/images/fsa.png similarity index 100% rename from doc/analyzers/images/fsa.png rename to docfx/images/fsa.png diff --git a/doc/images/vs_threadpoolstarvation_event.jpg b/docfx/images/vs_threadpoolstarvation_event.jpg similarity index 100% rename from doc/images/vs_threadpoolstarvation_event.jpg rename to docfx/images/vs_threadpoolstarvation_event.jpg diff --git a/docfx/index.md b/docfx/index.md index 391ee6ec5..637535dda 100644 --- a/docfx/index.md +++ b/docfx/index.md @@ -4,6 +4,7 @@ _layout: landing # Overview -This is your docfx landing page. +This is the documentation site for the Microsoft.VisualStudio.Threading library and analyzers. -Click "Docs" across the top to get started. +Click the Docs or Analyzers heading on the top to read the docs. +Or click the API heading to see API-level documentation. diff --git a/docfx/toc.yml b/docfx/toc.yml index 8e9a67007..cf4bdcb5a 100644 --- a/docfx/toc.yml +++ b/docfx/toc.yml @@ -1,5 +1,9 @@ items: - name: Docs href: docs/ +- name: Analyzers + href: analyzers/ - name: API href: api/ +- name: GitHub + href: https://github.com/microsoft/vs-threading diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers.CodeFixes/README.md b/src/Microsoft.VisualStudio.Threading.Analyzers.CodeFixes/README.md index b6955d52e..0662334ab 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers.CodeFixes/README.md +++ b/src/Microsoft.VisualStudio.Threading.Analyzers.CodeFixes/README.md @@ -2,4 +2,4 @@ Static code analyzers to detect common mistakes or potential issues regarding threading and async coding. -[Diagnostic analyzer rules](https://github.com/microsoft/vs-threading/blob/main/doc/analyzers/index.md). +[Diagnostic analyzer rules](https://microsoft.github.io/vs-threading/analyzers/index.html). diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/Utils.cs b/src/Microsoft.VisualStudio.Threading.Analyzers/Utils.cs index d3cfd4a2c..047c50c0a 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/Utils.cs +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/Utils.cs @@ -481,7 +481,7 @@ public static void Deconstruct(this Tuple tuple, public static string GetHelpLink(string analyzerId) { - return $"https://github.com/Microsoft/vs-threading/blob/main/doc/analyzers/{analyzerId}.md"; + return $"https://microsoft.github.io/vs-threading/analyzers/{analyzerId}.html"; } /// diff --git a/src/Microsoft.VisualStudio.Threading/README.md b/src/Microsoft.VisualStudio.Threading/README.md index 465ce9f1d..96928a56c 100644 --- a/src/Microsoft.VisualStudio.Threading/README.md +++ b/src/Microsoft.VisualStudio.Threading/README.md @@ -2,6 +2,8 @@ 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). +[Full documentation](https://microsoft.github.io/vs-threading/docs/getting-started.html). + ## Features * Async versions of many threading synchronization primitives From a8359f99b3bf0fc7d08c9e4bf9a47c8ce30ded50 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 6 Feb 2025 10:37:33 -0700 Subject: [PATCH 2/2] Don't update xunit.combinatorial to v2 --- .github/renovate.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/renovate.json b/.github/renovate.json index 3b72ae384..09fd2fa77 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -16,6 +16,10 @@ "matchPackageNames": ["xunit.runner.visualstudio"], "allowedVersions": "<3.0" }, + { + "matchPackageNames": ["xunit.combinatorial"], + "allowedVersions": "<2.0" + }, { "matchDatasources": ["dotnet-version", "docker"], "matchDepNames": ["dotnet-sdk", "mcr.microsoft.com/dotnet/sdk"],