@@ -13,27 +13,27 @@ extensions](cookbook_vs.md)).
1313
1414## The Rules
1515
16- 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 ) .
16+ 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 ) .
1717
1818### Rule #1 . If a method has certain thread apartment requirements (STA or MTA) it must either:
1919 1 . Have an asynchronous signature, and asynchronously marshal to the appropriate
20- thread if it isn't originally invoked on a compatible thread. The recommended
20+ thread if it isn't originally invoked on a compatible thread. The recommended
2121 means of switching to the main thread is:
2222
2323 ``` csharp
2424 await joinableTaskFactoryInstance .SwitchToMainThreadAsync ();
2525 ```
2626
2727 OR
28-
28+
2929 2 . Have a synchronous signature , and throw an exception when called on the wrong thread .
3030 This can be done in Visual Studio with `ThreadHelper .ThrowIfNotOnUIThread ()` or
3131 `ThreadHelper .ThrowIfOnUIThread ()`.
3232
3333 In particular , no method is allowed to synchronously marshal work to
3434 another thread (blocking while that work is done ) except by using the second rule (below ).
3535 Synchronous blocks in general are to be avoided whenever possible .
36-
36+
3737### 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:
3838
3939```csharp
@@ -42,7 +42,7 @@ joinableTaskFactoryInstance.Run(async delegate
4242 await SomeOperationAsync (.. .);
4343});
4444```
45-
45+
4646### Rule #3 . If ever awaiting work that was started earlier, that work must be * joined* .
4747
4848For example, one service kicks off some asynchronous work that may later become synchronously blocking:
@@ -57,22 +57,22 @@ JoinableTask longRunningAsyncWork = joinableTaskFactoryInstance.RunAsync(
5757
5858then later that async work becomes blocking:
5959
60- ``` csharp
60+ ``` csharp
6161longRunningAsyncWork .Join ();
6262```
6363
64- or perhaps
64+ or perhaps
6565
66- ``` csharp
66+ ``` csharp
6767await longRunningAsyncWork ;
6868```
6969
7070Note however that this extra step is not necessary when awaiting is
7171done immediately after kicking off an asynchronous operation.
72-
73- In particular, no method should call ` Task.Wait() ` or ` Task.Result ` on
72+
73+ In particular, no method should call ` Task.Wait() ` or ` Task.Result ` on
7474an incomplete ` Task ` .
75-
75+
7676### Additional "honorable mention" rules: (Not JTF related)
7777
7878### Rule #4 . Never define ` async void ` methods. Make the methods return ` Task ` instead.
@@ -81,11 +81,11 @@ an incomplete `Task`.
8181 - Exceptions can't be reported to telemetry by the caller.
8282 - It's impossible for your VS package to responsibly block in ` Package.Close `
8383 till your ` async ` work is done when it was kicked off this way.
84- - Be cautious: ` async delegate ` or ` async () => ` become ` async void `
85- methods when passed to a method that accepts ` Action ` delegates. Only
86- pass ` async ` delegates to methods that accept ` Func<Task> ` or
84+ - Be cautious: ` async delegate ` or ` async () => ` become ` async void `
85+ methods when passed to a method that accepts ` Action ` delegates. Only
86+ pass ` async ` delegates to methods that accept ` Func<Task> ` or
8787 ` Func<Task<T>> ` parameters.
88-
88+
8989Frequently Asked Questions
9090---------------
9191
@@ -139,65 +139,65 @@ blocking thread to execute the continuations.
139139
140140There are several reasons for this:
141141
142- 1 . The COM transition synchronously blocks the calling thread. If the
143- main thread isn't immediately pumping messages, the MTA thread will
144- block until it handles the message. If you're on a threadpool thread,
145- this ties up a precious resource and if your code may execute on
146- multiple threadpool threads at once, there is a very real possibility
142+ 1 . The COM transition synchronously blocks the calling thread. If the
143+ main thread isn't immediately pumping messages, the MTA thread will
144+ block until it handles the message. If you're on a threadpool thread,
145+ this ties up a precious resource and if your code may execute on
146+ multiple threadpool threads at once, there is a very real possibility
147147 of [ threadpool starvation] ( threadpool_starvation.md ) .
148- 2 . Deadlock: if the main thread is blocked waiting for the background
149- thread, and the main thread happens to be on top of some call stack
150- (like WPF measure-layout) that suppresses the message pump, the code
148+ 2 . Deadlock: if the main thread is blocked waiting for the background
149+ thread, and the main thread happens to be on top of some call stack
150+ (like WPF measure-layout) that suppresses the message pump, the code
151151 that normally works will randomly deadlock.
152- 3 . When the main thread is pumping messages, it will execute your code,
153- regardless as to whether it is relevant to what the main thread may
154- already be doing. If the main thread is in the main message pump,
155- that's fine. But if the main thread is in a pumping wait (in
156- managed code this could be almost anywhere as this includes locks,
157- I/O, sync blocks, etc.) it could be a very bad time. We call these
158- bad times "reentrancy" and the problem comes when you have component
159- X running on the main thread in a pumping wait, the component Y
160- uses COM marshalling to re-enter the main thread, and then Y calls
161- (directly or indirectly) into component X. Component X is typically
162- written with the assumption that by being on the main thread, it's
163- isolated and single-threaded, and it usually isn't prepared to handle
164- reentrancy. As a result, data corruption and/or deadlocks can result.
165- Such has been the source of many deadlocks and crashes in VS for the
152+ 3 . When the main thread is pumping messages, it will execute your code,
153+ regardless as to whether it is relevant to what the main thread may
154+ already be doing. If the main thread is in the main message pump,
155+ that's fine. But if the main thread is in a pumping wait (in
156+ managed code this could be almost anywhere as this includes locks,
157+ I/O, sync blocks, etc.) it could be a very bad time. We call these
158+ bad times "reentrancy" and the problem comes when you have component
159+ X running on the main thread in a pumping wait, the component Y
160+ uses COM marshalling to re-enter the main thread, and then Y calls
161+ (directly or indirectly) into component X. Component X is typically
162+ written with the assumption that by being on the main thread, it's
163+ isolated and single-threaded, and it usually isn't prepared to handle
164+ reentrancy. As a result, data corruption and/or deadlocks can result.
165+ Such has been the source of many deadlocks and crashes in VS for the
166166 last few releases.
167- 4 . Any method from a VS service that returns a pointer is probably
168- inherently broken when called from a background thread. For example,
169- ` ItemID ` s returned from ` IVsHierarchy ` are very often raw pointers cast
170- to integers. These pointers are guaranteed to be valid for as long
171- as you're on the main thread (and no event was raised to invalidate
172- it). But when you call a ` IVsHierarchy ` method to get an ` ItemID ` back
173- from a background thread, you leave the STA thread immediately as
174- the call returns, meaning the pointer is unsafe to use. If you then
175- go and pass that pointer back into the project system, the pointer
176- could have been invalidated in the interim, and you'll end up causing
177- an access violation crash in VS. The only safe way to deal with
178- ` ItemID ` s (or any other pointer type) is while manually marshaled to
179- the UI thread so that you know they are still valid for as long as
167+ 4 . Any method from a VS service that returns a pointer is probably
168+ inherently broken when called from a background thread. For example,
169+ ` ItemID ` s returned from ` IVsHierarchy ` are very often raw pointers cast
170+ to integers. These pointers are guaranteed to be valid for as long
171+ as you're on the main thread (and no event was raised to invalidate
172+ it). But when you call a ` IVsHierarchy ` method to get an ` ItemID ` back
173+ from a background thread, you leave the STA thread immediately as
174+ the call returns, meaning the pointer is unsafe to use. If you then
175+ go and pass that pointer back into the project system, the pointer
176+ could have been invalidated in the interim, and you'll end up causing
177+ an access violation crash in VS. The only safe way to deal with
178+ ` ItemID ` s (or any other pointer type) is while manually marshaled to
179+ the UI thread so that you know they are still valid for as long as
180180 you hold and use them.
181- 5 . If your method runs on a background thread and has a loop that
182- accesses a VS service, that can incur a lot of thread transitions
183- which can hurt performance. If you were explicit in your code about
184- the transition, you'd very likely move it to just before you enter
181+ 5 . If your method runs on a background thread and has a loop that
182+ accesses a VS service, that can incur a lot of thread transitions
183+ which can hurt performance. If you were explicit in your code about
184+ the transition, you'd very likely move it to just before you enter
185185 the loop, which would make your code more efficient from the start.
186- 6 . Some VS services don't have proxy stubs registered and thus will fail
187- to the type cast or on method invocation when your code executes on
186+ 6 . Some VS services don't have proxy stubs registered and thus will fail
187+ to the type cast or on method invocation when your code executes on
188188 a background thread.
189- 7 . Some VS services get rewritten from native to managed code, which
190- subtly changes them from single-threaded to free-threaded services.
191- Unless the managed code is written to be thread-safe (most is not)
192- this means that your managed code calling into a managed code VS
193- service on a background thread will not transition to the UI thread
194- first, and you are cruising for thread-safety bugs (data corruption,
195- crashes, hangs, etc). By switching to the main thread yourself first,
196- you won't be the poor soul who has crashes in their feature and has
197- to debug it for days until you finally figure out that you were causing
198- data corruption and a crash later on. Yes, you can blame the free
199- threaded managed code that should have protected itself, but that's
200- not very satisfying after days of investigation. And the owner of
189+ 7 . Some VS services get rewritten from native to managed code, which
190+ subtly changes them from single-threaded to free-threaded services.
191+ Unless the managed code is written to be thread-safe (most is not)
192+ this means that your managed code calling into a managed code VS
193+ service on a background thread will not transition to the UI thread
194+ first, and you are cruising for thread-safety bugs (data corruption,
195+ crashes, hangs, etc). By switching to the main thread yourself first,
196+ you won't be the poor soul who has crashes in their feature and has
197+ to debug it for days until you finally figure out that you were causing
198+ data corruption and a crash later on. Yes, you can blame the free
199+ threaded managed code that should have protected itself, but that's
200+ not very satisfying after days of investigation. And the owner of
201201 that code may refuse to fix their code and you'll have to fix yours anyway.
202202
203203##### How do these rules protect me from re-entering random code on the main thread?
@@ -243,7 +243,7 @@ the moment. The debugger and Windows teams are working to improve that
243243situation. In the meantime, we have learned several techniques to figure
244244out what is causing the hang, and we're working to enhance the framework
245245to automatically detect, self-analyze and report hangs to you so you have
246- almost nothing to do but fix the code bug.
246+ almost nothing to do but fix the code bug.
247247
248248In the meantime, the most useful technique for analyzing async hangs is to
249249attach WinDBG to the process and dump out incomplete async methods' states.
@@ -283,7 +283,7 @@ priority via the `JoinableTask` it may call your code within.
283283
284284##### What message priority is used to switch to (or resume on) the main thread, and can this be changed?
285285
286- ` JoinableTaskFactory ` ’ s default behavior is to switch to the main thread using
286+ ` JoinableTaskFactory ` ' s default behavior is to switch to the main thread using
287287` SynchronizationContext.Post ` , which typically posts a message to the main thread,
288288which puts it below RPC and above user input in priority.
289289
@@ -303,8 +303,8 @@ your own constructor that chains in the base constructor, passing in the
303303required parameters. You are then free to directly instantiate your derived
304304type by passing in either a ` JoinableTaskContext ` or a ` JoinableTaskCollection ` .
305305
306- For more information on this topic, see Andrew Arnott's blog post
307- [ Asynchronous and multithreaded programming within VS using the
306+ For more information on this topic, see Andrew Arnott's blog post
307+ [ Asynchronous and multithreaded programming within VS using the
308308` JoinableTaskFactory ` ] [ JTFBlog ] .
309309
310310[ AsyncHangDebugging ] : https://github.com/Microsoft/VSProjectSystem/blob/master/doc/scenario/analyze_hangs.md
0 commit comments