Skip to content

Commit

Permalink
Fix mlc markdown link errors, warnings
Browse files Browse the repository at this point in the history
  • Loading branch information
AArnott committed Feb 5, 2025
1 parent 0beeb5c commit 3b02914
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 80 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/docs_validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: 🔗 Markup Link Checker (mlc)
uses: becheran/mlc@v0.19.2
with:
args: --do-not-warn-for-redirect-to https://learn.microsoft.com*,https://dotnet.microsoft.com/*,https://dev.azure.com/*,https://app.codecov.io/* -p docfx -i https://aka.ms/onboardsupport,https://aka.ms/spot,https://msrc.microsoft.com/*,https://www.microsoft.com/msrc*,https://microsoft.com/msrc*,https://microsoft.sharepoint.com/*
args: --do-not-warn-for-redirect-to https://learn.microsoft.com*,https://dotnet.microsoft.com/*,https://dev.azure.com/*,https://app.codecov.io/*,https://badges.gitter.im/*,https://github.com/*,https://app.gitter.im/* -p docfx -i https://aka.ms/onboardsupport,https://aka.ms/spot,https://msrc.microsoft.com/*,https://www.microsoft.com/msrc*,https://microsoft.com/msrc*,https://microsoft.sharepoint.com/*
- name: ⚙ Install prerequisites
run: |
./init.ps1 -UpgradePrerequisites
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ For significant changes we strongly recommend opening an issue to start a design

* [.NET Core SDK](https://dotnet.microsoft.com/download/dotnet-core/2.2) with the version matching our [global.json](global.json) file. The version you install must be at least the version specified in the global.json file, and must be within the same hundreds version for the 3rd integer: x.y.Czz (x.y.C must match, and zz must be at least as high).
The easiest way to get this is to run the `init` script at the root of the repo. Use the `-InstallLocality Machine` and approve admin elevation if you wish so the SDK is always discoverable from VS. See the `init` script usage doc for more details.
* Optional: [Visual Studio 2019](https://www.visualstudio.com/)
* Optional: [Visual Studio 2022](https://visualstudio.microsoft.com/)

The only prerequisite for building, testing, and deploying from this repository
is the [.NET SDK](https://get.dot.net/).
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

## Microsoft.VisualStudio.Threading

[![NuGet package](https://img.shields.io/nuget/v/Microsoft.VisualStudio.Threading.svg)](https://nuget.org/packages/Microsoft.VisualStudio.Threading)
[![NuGet package](https://img.shields.io/nuget/v/Microsoft.VisualStudio.Threading.svg)](https://www.nuget.org/packages/Microsoft.VisualStudio.Threading)

Async synchronization primitives, async collections, TPL and dataflow extensions. The JoinableTaskFactory allows synchronously blocking the UI thread for async work. This package is applicable to any .NET application (not just Visual Studio).

Expand All @@ -15,7 +15,7 @@ Async synchronization primitives, async collections, TPL and dataflow extensions

## Microsoft.VisualStudio.Threading.Analyzers

[![NuGet package](https://img.shields.io/nuget/v/Microsoft.VisualStudio.Threading.Analyzers.svg)](https://nuget.org/packages/Microsoft.VisualStudio.Threading.Analyzers)
[![NuGet package](https://img.shields.io/nuget/v/Microsoft.VisualStudio.Threading.Analyzers.svg)](https://www.nuget.org/packages/Microsoft.VisualStudio.Threading.Analyzers)

Static code analyzer to detect common mistakes or potential issues regarding threading and async coding.

Expand Down
2 changes: 1 addition & 1 deletion doc/analyzers/VSTHRD010.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@ private async Task CallVSAsync()
}
```

Refer to [Asynchronous and multithreaded programming within VS using the JoinableTaskFactory](http://blogs.msdn.com/b/andrewarnottms/archive/2014/05/07/asynchronous-and-multithreaded-programming-within-vs-using-the-joinabletaskfactory/) for more info.
Refer to [Asynchronous and multithreaded programming within VS using the JoinableTaskFactory](https://devblogs.microsoft.com/premier-developer/asynchronous-and-multithreaded-programming-within-vs-using-the-joinabletaskfactory/) for more info.
144 changes: 72 additions & 72 deletions doc/threading_rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,27 @@ extensions](cookbook_vs.md)).

## The Rules

The rules are listed below with minimal examples. For a more thorough explanation with more examples, check out [this slideshow](https://www.slideshare.net/aarnott/the-3-vs-threading-rules).
The rules are listed below with minimal examples. For a more thorough explanation with more examples, check out [this slideshow](https://www.slideshare.net/slideshow/the-3-vs-threading-rules/78280010).

### Rule #1. If a method has certain thread apartment requirements (STA or MTA) it must either:
1. Have an asynchronous signature, and asynchronously marshal to the appropriate
thread if it isn't originally invoked on a compatible thread. The recommended
thread if it isn't originally invoked on a compatible thread. The recommended
means of switching to the main thread is:

```csharp
await joinableTaskFactoryInstance.SwitchToMainThreadAsync();
```

OR

2. Have a synchronous signature, and throw an exception when called on the wrong thread.
This can be done in Visual Studio with `ThreadHelper.ThrowIfNotOnUIThread()` or
`ThreadHelper.ThrowIfOnUIThread()`.

In particular, no method is allowed to synchronously marshal work to
another thread (blocking while that work is done) except by using the second rule (below).
Synchronous blocks in general are to be avoided whenever possible.

### Rule #2. When an implementation of an already-shipped public API must call asynchronous code and block for its completion, it must do so by following this simple pattern:

```csharp
Expand All @@ -42,7 +42,7 @@ joinableTaskFactoryInstance.Run(async delegate
await SomeOperationAsync(...);
});
```

### Rule #3. If ever awaiting work that was started earlier, that work must be *joined*.

For example, one service kicks off some asynchronous work that may later become synchronously blocking:
Expand All @@ -57,22 +57,22 @@ JoinableTask longRunningAsyncWork = joinableTaskFactoryInstance.RunAsync(

then later that async work becomes blocking:

```csharp
```csharp
longRunningAsyncWork.Join();
```

or perhaps
or perhaps

```csharp
```csharp
await longRunningAsyncWork;
```

Note however that this extra step is not necessary when awaiting is
done immediately after kicking off an asynchronous operation.
In particular, no method should call `Task.Wait()` or `Task.Result` on

In particular, no method should call `Task.Wait()` or `Task.Result` on
an incomplete `Task`.

### Additional "honorable mention" rules: (Not JTF related)

### Rule #4. Never define `async void` methods. Make the methods return `Task` instead.
Expand All @@ -81,11 +81,11 @@ an incomplete `Task`.
- Exceptions can't be reported to telemetry by the caller.
- It's impossible for your VS package to responsibly block in `Package.Close`
till your `async` work is done when it was kicked off this way.
- Be cautious: `async delegate` or `async () =>` become `async void`
methods when passed to a method that accepts `Action` delegates. Only
pass `async` delegates to methods that accept `Func<Task>` or
- Be cautious: `async delegate` or `async () =>` become `async void`
methods when passed to a method that accepts `Action` delegates. Only
pass `async` delegates to methods that accept `Func<Task>` or
`Func<Task<T>>` parameters.

Frequently Asked Questions
---------------

Expand Down Expand Up @@ -139,65 +139,65 @@ blocking thread to execute the continuations.

There are several reasons for this:

1. The COM transition synchronously blocks the calling thread. If the
main thread isn't immediately pumping messages, the MTA thread will
block until it handles the message. If you're on a threadpool thread,
this ties up a precious resource and if your code may execute on
multiple threadpool threads at once, there is a very real possibility
1. The COM transition synchronously blocks the calling thread. If the
main thread isn't immediately pumping messages, the MTA thread will
block until it handles the message. If you're on a threadpool thread,
this ties up a precious resource and if your code may execute on
multiple threadpool threads at once, there is a very real possibility
of [threadpool starvation](threadpool_starvation.md).
2. Deadlock: if the main thread is blocked waiting for the background
thread, and the main thread happens to be on top of some call stack
(like WPF measure-layout) that suppresses the message pump, the code
2. Deadlock: if the main thread is blocked waiting for the background
thread, and the main thread happens to be on top of some call stack
(like WPF measure-layout) that suppresses the message pump, the code
that normally works will randomly deadlock.
3. When the main thread is pumping messages, it will execute your code,
regardless as to whether it is relevant to what the main thread may
already be doing. If the main thread is in the main message pump,
that's fine. But if the main thread is in a pumping wait (in
managed code this could be almost anywhere as this includes locks,
I/O, sync blocks, etc.) it could be a very bad time. We call these
bad times "reentrancy" and the problem comes when you have component
X running on the main thread in a pumping wait, the component Y
uses COM marshalling to re-enter the main thread, and then Y calls
(directly or indirectly) into component X. Component X is typically
written with the assumption that by being on the main thread, it's
isolated and single-threaded, and it usually isn't prepared to handle
reentrancy. As a result, data corruption and/or deadlocks can result.
Such has been the source of many deadlocks and crashes in VS for the
3. When the main thread is pumping messages, it will execute your code,
regardless as to whether it is relevant to what the main thread may
already be doing. If the main thread is in the main message pump,
that's fine. But if the main thread is in a pumping wait (in
managed code this could be almost anywhere as this includes locks,
I/O, sync blocks, etc.) it could be a very bad time. We call these
bad times "reentrancy" and the problem comes when you have component
X running on the main thread in a pumping wait, the component Y
uses COM marshalling to re-enter the main thread, and then Y calls
(directly or indirectly) into component X. Component X is typically
written with the assumption that by being on the main thread, it's
isolated and single-threaded, and it usually isn't prepared to handle
reentrancy. As a result, data corruption and/or deadlocks can result.
Such has been the source of many deadlocks and crashes in VS for the
last few releases.
4. Any method from a VS service that returns a pointer is probably
inherently broken when called from a background thread. For example,
`ItemID`s returned from `IVsHierarchy` are very often raw pointers cast
to integers. These pointers are guaranteed to be valid for as long
as you're on the main thread (and no event was raised to invalidate
it). But when you call a `IVsHierarchy` method to get an `ItemID` back
from a background thread, you leave the STA thread immediately as
the call returns, meaning the pointer is unsafe to use. If you then
go and pass that pointer back into the project system, the pointer
could have been invalidated in the interim, and you'll end up causing
an access violation crash in VS. The only safe way to deal with
`ItemID`s (or any other pointer type) is while manually marshaled to
the UI thread so that you know they are still valid for as long as
4. Any method from a VS service that returns a pointer is probably
inherently broken when called from a background thread. For example,
`ItemID`s returned from `IVsHierarchy` are very often raw pointers cast
to integers. These pointers are guaranteed to be valid for as long
as you're on the main thread (and no event was raised to invalidate
it). But when you call a `IVsHierarchy` method to get an `ItemID` back
from a background thread, you leave the STA thread immediately as
the call returns, meaning the pointer is unsafe to use. If you then
go and pass that pointer back into the project system, the pointer
could have been invalidated in the interim, and you'll end up causing
an access violation crash in VS. The only safe way to deal with
`ItemID`s (or any other pointer type) is while manually marshaled to
the UI thread so that you know they are still valid for as long as
you hold and use them.
5. If your method runs on a background thread and has a loop that
accesses a VS service, that can incur a lot of thread transitions
which can hurt performance. If you were explicit in your code about
the transition, you'd very likely move it to just before you enter
5. If your method runs on a background thread and has a loop that
accesses a VS service, that can incur a lot of thread transitions
which can hurt performance. If you were explicit in your code about
the transition, you'd very likely move it to just before you enter
the loop, which would make your code more efficient from the start.
6. Some VS services don't have proxy stubs registered and thus will fail
to the type cast or on method invocation when your code executes on
6. Some VS services don't have proxy stubs registered and thus will fail
to the type cast or on method invocation when your code executes on
a background thread.
7. Some VS services get rewritten from native to managed code, which
subtly changes them from single-threaded to free-threaded services.
Unless the managed code is written to be thread-safe (most is not)
this means that your managed code calling into a managed code VS
service on a background thread will not transition to the UI thread
first, and you are cruising for thread-safety bugs (data corruption,
crashes, hangs, etc). By switching to the main thread yourself first,
you won't be the poor soul who has crashes in their feature and has
to debug it for days until you finally figure out that you were causing
data corruption and a crash later on. Yes, you can blame the free
threaded managed code that should have protected itself, but that's
not very satisfying after days of investigation. And the owner of
7. Some VS services get rewritten from native to managed code, which
subtly changes them from single-threaded to free-threaded services.
Unless the managed code is written to be thread-safe (most is not)
this means that your managed code calling into a managed code VS
service on a background thread will not transition to the UI thread
first, and you are cruising for thread-safety bugs (data corruption,
crashes, hangs, etc). By switching to the main thread yourself first,
you won't be the poor soul who has crashes in their feature and has
to debug it for days until you finally figure out that you were causing
data corruption and a crash later on. Yes, you can blame the free
threaded managed code that should have protected itself, but that's
not very satisfying after days of investigation. And the owner of
that code may refuse to fix their code and you'll have to fix yours anyway.

##### How do these rules protect me from re-entering random code on the main thread?
Expand Down Expand Up @@ -243,7 +243,7 @@ the moment. The debugger and Windows teams are working to improve that
situation. In the meantime, we have learned several techniques to figure
out what is causing the hang, and we're working to enhance the framework
to automatically detect, self-analyze and report hangs to you so you have
almost nothing to do but fix the code bug.
almost nothing to do but fix the code bug.

In the meantime, the most useful technique for analyzing async hangs is to
attach WinDBG to the process and dump out incomplete async methods' states.
Expand Down Expand Up @@ -283,7 +283,7 @@ priority via the `JoinableTask` it may call your code within.

##### What message priority is used to switch to (or resume on) the main thread, and can this be changed?

`JoinableTaskFactory`s default behavior is to switch to the main thread using
`JoinableTaskFactory`'s default behavior is to switch to the main thread using
`SynchronizationContext.Post`, which typically posts a message to the main thread,
which puts it below RPC and above user input in priority.

Expand All @@ -303,8 +303,8 @@ your own constructor that chains in the base constructor, passing in the
required parameters. You are then free to directly instantiate your derived
type by passing in either a `JoinableTaskContext` or a `JoinableTaskCollection`.

For more information on this topic, see Andrew Arnott's blog post
[Asynchronous and multithreaded programming within VS using the
For more information on this topic, see Andrew Arnott's blog post
[Asynchronous and multithreaded programming within VS using the
`JoinableTaskFactory`][JTFBlog].

[AsyncHangDebugging]: https://github.com/Microsoft/VSProjectSystem/blob/master/doc/scenario/analyze_hangs.md
Expand Down
Loading

0 comments on commit 3b02914

Please sign in to comment.