Merge branch 'master' into STMTAdoc

This commit is contained in:
Andrew Arnott 2018-12-14 13:34:52 -08:00
Родитель 2d2acad8be be5904c33b
Коммит 0250cff66b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: A9B9910CDCCDA441
102 изменённых файлов: 5127 добавлений и 1699 удалений

46
.github/ISSUE_TEMPLATE/Bug_report.md поставляемый
Просмотреть файл

@ -1,23 +1,23 @@
---
name: Bug report
about: Create a report to help us improve
---
#### Bug description
A clear and concise description of what the bug is.
#### Repro steps
Code to reproduce the behavior.
#### Expected behavior
A clear and concise description of what you expected to happen.
#### Actual behavior
What happened instead of what you expected.
- Version used:
- Application (if applicable):
#### Additional context
Add any other context about the problem here.
---
name: Bug report
about: Create a report to help us improve
---
#### Bug description
A clear and concise description of what the bug is.
#### Repro steps
Code to reproduce the behavior.
#### Expected behavior
A clear and concise description of what you expected to happen.
#### Actual behavior
What happened instead of what you expected.
- Version used:
- Application (if applicable):
#### Additional context
Add any other context about the problem here.

34
.github/ISSUE_TEMPLATE/Feature_request.md поставляемый
Просмотреть файл

@ -1,17 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
---
#### Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
#### Describe the solution you'd like
A clear and concise description of what you want to happen.
#### Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
#### Additional context
Add any other context or screenshots about the feature request here.
---
name: Feature request
about: Suggest an idea for this project
---
#### Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
#### Describe the solution you'd like
A clear and concise description of what you want to happen.
#### Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
#### Additional context
Add any other context or screenshots about the feature request here.

Просмотреть файл

@ -18,10 +18,11 @@ Analyzers: [![NuGet package](https://img.shields.io/nuget/v/Microsoft.VisualStud
* `AsyncSemaphore`
* `ReentrantSemaphore`
* Async versions of very common types
* `AsyncEventHandler`
* `AsyncLazy<T>`
* `AsyncLazyInitializer`
* `AsyncLocal<T>`
* `AsyncQueue<T>`
* `AsyncEventHandler`
* Await extension methods
* Await on a `TaskScheduler` to switch to it.
Switch to a background thread with `await TaskScheduler.Default;`

Просмотреть файл

@ -26,3 +26,8 @@ void Foo() {
});
}
```
## Configuration
This analyzer is configurable via the `vs-threading.LegacyThreadSwitchingMembers.txt` file.
See our [configuration](configuration.md) topic for more information.

Просмотреть файл

@ -1,10 +1,20 @@
# VSTHRD003 Avoid awaiting non-joinable tasks in join contexts
# VSTHRD003 Avoid awaiting foreign Tasks
Tasks created from async methods executed outside of a JoinableTaskFactory.Run
delegate cannot be safely awaited inside one.
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.
## 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()
{
@ -16,6 +26,31 @@ void MyMethod()
}
```
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());
}
}
```
## Solution
To await the result of an async method from with a JoinableTaskFactory.Run delegate,
@ -35,12 +70,23 @@ void MyMethod()
Alternatively wrap the original method invocation with JoinableTaskFactory.RunAsync:
```csharp
void MyMethod()
class SomeClass
{
JoinableTask joinableTask = joinableTaskFactory.RunAsync(() => SomeOperationAsync());
joinableTaskFactory.Run(async delegate
JoinableTask joinableTask;
SomeClass()
{
await joinableTask;
});
this.joinableTask = joinableTaskFactory.RunAsync(() => SomeOperationAsync());
}
async Task MyMethodAsync()
{
await this.joinableTask;
}
void WaitForMyMethod()
{
joinableTaskFactory.Run(() => MyMethodAsync());
}
}
```

Просмотреть файл

@ -66,3 +66,16 @@ These are identified as described below:
Properties are specified by their name, not the name of their accessors.
For example, a property should be specified by `PropertyName`, not `get_PropertyName`.
## Legacy thread switching members
Prior to Microsoft.VisualStudio.Threading, libraries provided additional ways to execute
code on the UI thread. These methods should be avoided, and code should update to using
`JoinableTaskFactory.SwitchToMainThreadAsync()` as the standard way to switch to the main
thread.
**Filename:** `vs-threading.LegacyThreadSwitchingMembers.txt`
**Line format:** `[Namespace.TypeName]::MethodName`
**Sample:** `[System.Windows.Threading.Dispatcher]::Invoke`

Просмотреть файл

@ -5,9 +5,9 @@ NuGet package.
ID | Title | Severity | Supports
---- | --- | --- | --- |
[VSTHRD001](VSTHRD001.md) | Use JTF.SwitchToMainThread to switch | Critical | [1st rule](../threading_rules.md#Rule1)
[VSTHRD002](VSTHRD002.md) | Use JTF.Run to block | Critical | [2nd rule](../threading_rules.md#Rule2)
[VSTHRD003](VSTHRD003.md) | Use JTF.RunAsync to block later | Critical | [3rd rule](../threading_rules.md#Rule3)
[VSTHRD001](VSTHRD001.md) | Avoid legacy thread switching methods | Critical | [1st rule](../threading_rules.md#Rule1)
[VSTHRD002](VSTHRD002.md) | Avoid problematic synchronous waits | Critical | [2nd rule](../threading_rules.md#Rule2)
[VSTHRD003](VSTHRD003.md) | Avoid awaiting foreign Tasks | Critical | [3rd rule](../threading_rules.md#Rule3)
[VSTHRD004](VSTHRD004.md) | Await SwitchToMainThreadAsync | Critical | [1st rule](../threading_rules.md#Rule1)
[VSTHRD010](VSTHRD010.md) | Invoke single-threaded types on Main thread | Critical | [1st rule](../threading_rules.md#Rule1)
[VSTHRD011](VSTHRD011.md) | Use `AsyncLazy<T>` | Critical | [3rd rule](../threading_rules.md#Rule3)
@ -15,9 +15,9 @@ ID | Title | Severity | Supports
[VSTHRD100](VSTHRD100.md) | Avoid `async void` methods | Advisory
[VSTHRD101](VSTHRD101.md) | Avoid unsupported async delegates | Advisory | [VSTHRD100](VSTHRD100.md)
[VSTHRD102](VSTHRD102.md) | Implement internal logic asynchronously | Advisory | [2nd rule](../threading_rules.md#Rule2)
[VSTHRD103](VSTHRD103.md) | Use async option | Advisory
[VSTHRD103](VSTHRD103.md) | Call async methods when in an async method | Advisory
[VSTHRD104](VSTHRD104.md) | Offer async option | Advisory
[VSTHRD105](VSTHRD105.md) | Avoid implicit use of TaskScheduler.Current | Advisory
[VSTHRD105](VSTHRD105.md) | Avoid method overloads that assume `TaskScheduler.Current` | Advisory
[VSTHRD106](VSTHRD106.md) | Use `InvokeAsync` to raise async events | Advisory
[VSTHRD107](VSTHRD107.md) | Await Task within using expression | Advisory
[VSTHRD108](VSTHRD108.md) | Assert thread affinity unconditionally | Advisory | [1st rule](../threading_rules.md#Rule1), [VSTHRD010](VSTHRD010.md)

Просмотреть файл

@ -69,7 +69,7 @@ unless you specify the optional `alwaysYield: true` argument to the method.
When you definitely do not want the code following the transition to the main thread to execute if the token has been canceled, you should follow up the request for the main thread with a call to `cancellationToken.ThrowIfCancellationRequested();`
## How to switch to or use the UI thread with background priority
## How to switch to or use the UI thread with a specific priority
For those times when you need the UI thread but you don't want to introduce UI delays for the user,
you can use the `StartOnIdle` extension method which will run your code on the UI thread when it is otherwise idle.
@ -88,6 +88,24 @@ await ThreadHelper.JoinableTaskFactory.StartOnIdle(
});
```
If you have a requirement for a specific priority (which may be higher or lower than background), you can use the `WithPriority` extension method, like this:
```csharp
var databindPriorityJTF = ThreadHelper.JoinableTaskFactory.WithPriority(someDispatcher, DispatcherPriority.DataBind);
await databindPriorityJTF.RunAsync(
async delegate
{
// The JTF instance you use to actually make the switch is irrelevant here.
// The priority is dictated by the scenario owner, which is the one
// running JTF.RunAsync at the bottom of the stack (or just above us in this sample).
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
});
```
The way the priority is dictated by the `JoinableTaskFactory` instance that `RunAsync` is called on rather than the one on which `SwitchToMainThreadAsync` is invoked allows you to set the priority for the scenario and then call arbitrary code in VS and expect all of the switches to the main thread that code might require to honor that priority that you set as the scenario owner.
There are several `WithPriority` extension method overloads, allowing you to typically match exactly the priority your code was accustomed to before switching to use `JoinableTaskFactory` for switching to the main thread.
## Call async code from a synchronous method (and block on the result)
```csharp

Двоичные данные
doc/images/blocked_time.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 128 KiB

Двоичные данные
doc/images/blocked_time_callers.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 291 KiB

Двоичные данные
doc/images/cpu_stacks_showing_threadpool_starvation.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 120 KiB

Двоичные данные
doc/images/vs_threadpoolstarvation_event.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 47 KiB

Просмотреть файл

@ -6,6 +6,7 @@
* [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

127
doc/library_with_jtf.md Normal file
Просмотреть файл

@ -0,0 +1,127 @@
# Authoring a library with a JoinableTaskFactory dependency
This document describes how to author a library that either itself requires the main thread of its hosting application, or may call out to other code that requires the main thread (e.g. via event handlers). In particular, this document presents ways of obtaining a `JoinableTaskContext` or `JoinableTaskFactory` from library code since only applications (not libraries) should instantiate a `JoinableTaskContext`.
Any instance of `JoinableTaskFactory` is related to a `JoinableTaskContext`. There should only be one instance of `JoinableTaskContext` for a given "main" thread in an application. But since the `Microsoft.VisualStudio.Threading` assembly does not define a static property by which to obtain this singleton, any library that uses `JoinableTaskFactory` must obtain the singleton `JoinableTaskContext` from the application that hosts it. It is the application's responsibility to create this singleton and make it available for the library to consume.
There are a few scenarios a library author may find themself in:
1. [The library targets an app that exposes the `JoinableTaskContext`](#appoffers)
1. [Singleton class with `JoinableTaskContext` property](#singleton)
1. [Accept `JoinableTaskContext` as a constructor argument](#ctor)
If your library is distributed via NuGet, you can help your users follow the threading rules required by `JoinableTaskFactory` by
making sure your users also get the threading analyzers installed into their projects. Do this by modifying your `PackageReference` on the vs-threading library to include `PrivateAssets="none"` so that analyzers are not suppressed:
```xml
<ProjectReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="[latest-stable-version]" PrivateAssets="none" />
```
It is safe to depend on the latest version of the `Microsoft.VisualStudio.Threading.Analyzers` package.
When referencing the `Microsoft.VisualStudio.Threading` package, the version you select should be no newer than the one used by the hosting application.
## <a name="appoffers"></a>The library targets an app that exposes the `JoinableTaskContext`
### The app uses static properties to expose a `JoinableTaskContext`
When a library targets just one application, that application may expose a `JoinableTaskContext` via a static property on a public class.
For example, Visual Studio exposes the `JoinableTaskContext` from its [`ThreadHelper.JoinableTaskContext`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.shell.threadhelper.joinabletaskcontext?view=visualstudiosdk-2017) property.
Look up the documentation for the application you are extending, or reach out to the application's authors to find out how to obtain the shared instance of `JoinableTaskContext`.
### The app exports a `JoinableTaskContext` via MEF or other IoC container
A library may use MEF or another IoC mechanism to import a `JoinableTaskContext` from its environment. In this way, the library may be rehostable across several applications that export `JoinableTaskContext`.
For example, Visual Studio exports the `JoinableTaskContext` via MEF since 15.3.
### *Some* apps the library targets export `JoinableTaskContext`
When a library runs in multiple apps, only a subset of which actually export a `JoinableTaskContext`, it can be cumbersome to write code to handle its presence and absence all the time. It may be more convenient to instantiate your own instance of `JoinableTaskContext` when in an application that does not export it. *Do this with care*, since there should only be *one* `JoinableTaskContext` for a given main thread in an application. If you create your own because MEF doesn't export it, but the application *does* in fact have a shared instance that is obtainable another way, you could be introducing deadlocks into that application. Be sure to only use this mechanism when you know the app(s) hosting your library either export the `JoinableTaskContext` or have none at all. The following code snippet shows how to conditionally import it, and then ensure you have something you can import everywhere else that is reliable:
```cs
[Export]
internal class ThreadingContext
{
[ImportingConstructor]
public ThreadingContext([Import(AllowDefault = true)] JoinableTaskContext joinableTaskContext)
{
// If no MEF export is found, we create our own instance.
// Our private instance will only work if this MEF part is activated on the main thread of the application
// since creating a JoinableTaskContext captures the thread and SynchronizationContext.
JoinableTaskContext = joinableTaskContext ?? new JoinableTaskContext();
}
/// <summary>
/// Gets the <see cref="Microsoft.VisualStudio.Threading.JoinableTaskContext" /> associated
/// with the application if there is one, otherwise a library-local instance.
/// </summary>
/// <devremarks>
/// DO NOT export this property directly, since that will lead to MEF observing TWO exports
/// in the apps that export this instance already, which will break everyone using this MEF export.
/// </devremarks>
public JoinableTaskContext JoinableTaskContext { get; }
}
```
The rest of your library can then import your `ThreadingContext` class:
```cs
internal class SomeUserOfJTF
{
[Import]
ThreadingContext ThreadingContext { get; set; }
public async Task SomeMainThreadMethodAsync()
{
await this.ThreadingContext.JoinableTaskContext.SwitchToMainThreadAsync();
// Do work here.
}
}
```
## <a name="singleton"></a>Singleton class with `JoinableTaskContext` property
If your library doesn't target any application specifically, the library can indicate in its documentation that to run successfully, a hosting application must set the `JoinableTaskContext` property exposed by the library. This works particularly well if your library has a natural entrypoint class where that `JoinableTaskContext` can be set. This may be a singleton/static class. For example:
```cs
public static class LibrarySettings
{
private JoinableTaskContext joinableTaskContext;
/// <summary>
/// Gets or sets the JoinableTaskContext created on the main thread of the application hosting this library.
/// </summary>
public static JoinableTaskContext JoinableTaskContext
{
get
{
if (this.joinableTaskContext == null)
{
// This self-initializer is for when an app does not have a `JoinableTaskContext` to pass to the library.
// Our private instance will only work if this property getter first runs on the main thread of the application
// since creating a JoinableTaskContext captures the thread and SynchronizationContext.
this.joinableTaskContext = new JoinableTaskContext();
}
return this.joinableTaskContext;
}
set
{
Assumes.True(this.joinableTaskContext == null || this.joinableTaskContext == value, "This property has already been set to another value or is set after its value has been retrieved with a self-created value. Set this property once, before it is used elsewhere.");
this.joinableTaskContext = value;
}
}
}
```
This pattern and self-initializer allows all the rest of your library code to assume JTF is always present (so you can use JTF.Run and JTF.RunAsync everywhere w/o feature that JTF will be null), and it mitigates all the deadlocks possible given the host constraints.
Note that when you create your own default instance of JoinableTaskContext (i.e. when the host doesn't), it will consider the thread you're on to be the main thread. If SynchronizationContext.Current != null it will capture it and use it to switch to the main thread when you ask it to (very similar to how VS works today), otherwise any request to SwitchToMainThreadAsync will never switch the thread (since no `SynchronizationContext` was supplied to do so) but otherwise JTF continues to work.
## <a name="ctor"></a>Accept `JoinableTaskContext` as a constructor argument
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<T>` 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.

Просмотреть файл

@ -10,30 +10,57 @@ A thread pool starvation issue usually manifests itself as a long blocked time o
In general CLR thread pool is designed short running tasks that don't block the threads and CLR will adjust active worker thread counts to maximize throughput. Because of this, long running tasks that block on network IO, other async tasks can cause problems in how CLR allocates threads. In order to avoid these issues, it is recommended to always use async waits even in thread pool threads to avoid blocking a thread with no actual work being done. There are async APIs available already to do file IO, network IO that should make this easier.
CLR maintains two different statistics for managing thread pool threads. First is the reserved threads which are the actual physical threads created/destroyed in the system. Second is the active thread count which is the subset of the first that are actually used for executing jobs.
CLR maintains two different statistics for managing thread pool threads. First is the reserved threads which are the actual physical threads created/destroyed in the system. Second is the active thread count which is the subset of the first that are actually used for executing jobs.
In summary, not all of the thread pools threads visible in traces are actively used by CLR to schedule jobs instead majority of them are kept in reserved state which makes it very hard to analyze starvation from CPU/thread time samples alone without knowing the active thread count available for work.
For the logic of making more threads available for work, CLR will follow a something similar to:
* For anything up to MinThreads, which is equal to number of cores by default, reserved threads will be created on demand and will be made available on demand.
* After that point, if more work is still queued CLR will slowly adjust available thread count, creating reserved threads as needed or promoting existing reserved threads to available state. As this happens CLR will continue to monitor work throughput rate.
* After that point, if more work is still queued CLR will slowly adjust available thread count, creating reserved threads as needed or promoting existing reserved threads to available state. As this happens CLR will continue to monitor work throughput rate.
* For cases where available thread count is higher than min count, the CLR algorithm above might also decrease available thread count even if more work is available to see if throughput increases with less parallel work. So the adjustments usually a follow an upward trending zig zag pattern when work is constantly available.
* Once thread pool queue is empty or number of queued items decreases, CLR will quickly retire available threads to reserved status. In traces we see this happening under a second for example if no more work is scheduled.
* Once thread pool queue is empty or number of queued items decreases, CLR will quickly retire available threads to reserved status. In traces we see this happening under a second for example if no more work is scheduled.
* Reserved threads will only be destroyed if they are not used for a while (in order of minutes). This is to avoid constant cost of thread creation/destruction.
On a quad core machine, there will be minimum of 4 active threads at any given time. A starvation may occur anytime these 4 active threads are blocked in a long running operation.
## Investigating the root cause of thread starvation
While CLR has thread pool ETW events to indicate thread starvation, these events may not be included in a trace due to their cost and volume. You can however use Thread Time view to analyze what work was going on in the thread pool during the time main thread was blocked to see if starvation was an issue or not.
We use [PerfView](https://aka.ms/perfview) for these investigations.
1. Open the trace and open Thread Time stacks view filtered to devenv. You need to open the view from PerfView main window instead of scenarios view. The one opened from scenarios view will not show work that was started before the scenario which might be important in this case.
1. In the time view, filter the times to correct time range to the time where main thread was blocked for background task that didn't start executing yet.
1. Make sure clr module symbols are resolved and filter using "IncPaths" to include clr!ThreadpoolMgr::ExecuteWorkRequest frame.
1. This will now show all thread pool threads, some of them will be doing work, some of them will be waiting to be activated by CLR.
1. In a thread pool exhaustion case, you will have 4 (or a number matching number of CPU cores) threads doing work or blocked on a handle wait as part of some work.
1. Usually after a second another one will start to execute the blocked task or a thread might finish executing with in that second that unblocks the task.
1. Note that per above, CLR keeps a lot of thread pool threads alive but don't actually use them for executing work immediately so just looking at active threads might be misleading.
### When to suspect thread pool starvation
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)
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.
#### Visual Studio specific tips
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)
### Investigation steps
While the CLR has thread pool ETW events to indicate thread starvation, these events are not included in a trace due by default to their cost and volume. You can however use the Thread Time Stacks view in PerfView to analyze what work was going on in the thread pool during the time the main thread was blocked to see if starvation was an issue or not.
1. Open the trace in PerfView and open the Thread Time stacks view filtered to the process you suspect may be experiencing thread pool starvation. You need to open the view from PerfView main window instead of scenarios view. The one opened from scenarios view will not show work that was started before the scenario which might be important in this case.
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)
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:
1. Switch synchronous waits to async awaits for I/O, a switch to the main/UI thread, or a semaphore.
1. Avoid lock contention by throttling down concurrency (perhaps to just one sequential operation at once) or using a lock-free design.
### Useful background
In a thread pool exhaustion case, you will have at least as many thread pool threads as you have CPU cores on the machine the trace was taken from. Once all thread pool threads have blocked at the same time, the CLR will wait one second before adding another thread to the threadpool in an attempt to make more progress. Each second that passes without progress another thread may be added.
The CLR may deactivate these excess thread pool threads *without killing them* such that they do not participate in dequeuing work from the thread pool. In these investigations then, the presence of a thread pool thread that isn't blocked by user code is _not_ a clear indication that starvation is not occurring. In fact it may still be occurring if all thread pool threads that the CLR deems to be "active" are blocked in user code.
## RPS specific notes
@ -55,3 +82,7 @@ The mitigation for this is to have the method that is executing on the thread po
When a component sends many work items to the thread pool in a short timeframe, the queue will grow to store them till one of the thread pool threads can execute them all. Any subsequently queued items will be added to the end of the queue, regardless of their relative priority in the application. When the queue is long, and a work item is appended to the end of the queue that is required for the UI of the application to feel responsive, the application can hang or feel sluggish due to the work that otherwise should be running in the background without impacting UI responsiveness.
The mitigation is for components that have many work items to send to the threadpool to throttle the rate at which new items are introduced to the threadpool to a reasonably small number. This helps keep the queue short, and thus any newly enqueued work will execute much sooner, keeping the application responsive. Throttling work can be done such that the CPU stays busy and the background work moving along quickly, but without sacrificing UI responsiveness. See [this blog post](https://blogs.msdn.microsoft.com/andrewarnottms/2017/05/11/limiting-concurrency-for-faster-and-more-responsive-apps/) for more information on how to easily throttle concurrent work.
## Learn more
Vance Morrison wrote [a blog post](https://blogs.msdn.microsoft.com/vancem/2018/10/16/diagnosing-net-core-threadpool-starvation-with-perfview-why-my-service-is-not-saturating-all-cores-or-seems-to-stall/) describing this situation as well.

Просмотреть файл

@ -0,0 +1,3 @@
[Microsoft.VisualStudio.Shell.ThreadHelper]::Invoke
[Microsoft.VisualStudio.Shell.ThreadHelper]::InvokeAsync
[Microsoft.VisualStudio.Shell.ThreadHelper]::BeginInvoke

Просмотреть файл

@ -111,6 +111,157 @@ class Tests
await Verify.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task ReportWarningWhenTaskIsReturnedDirectlyFromMethod()
{
var test = @"
using System.Threading.Tasks;
class Tests
{
private Task task;
public Task GetTask()
{
return task;
}
}
";
var expected = this.CreateDiagnostic(10, 16, 4);
await Verify.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task ReportWarningWhenTaskIsReturnedAwaitedFromMethod()
{
var test = @"
using System.Threading.Tasks;
class Tests
{
private Task<int> task;
public async Task<int> AwaitAndGetResult()
{
return await task;
}
}
";
var expected = this.CreateDiagnostic(10, 22, 4);
await Verify.VerifyAnalyzerAsync(test, expected);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task ReportWarningWhenConfiguredTaskIsReturnedAwaitedFromMethod(bool continueOnCapturedContext)
{
var test = $@"
using System.Threading.Tasks;
class Tests
{{
private Task task;
public async Task AwaitAndGetResult()
{{
await task.ConfigureAwait({(continueOnCapturedContext ? "true" : "false")});
}}
}}
";
var expected = this.CreateDiagnostic(10, 15, 21 + continueOnCapturedContext.ToString().Length);
await Verify.VerifyAnalyzerAsync(test, expected);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task ReportWarningWhenConfiguredTaskTIsReturnedAwaitedFromMethod(bool continueOnCapturedContext)
{
var test = $@"
using System.Threading.Tasks;
class Tests
{{
private Task<int> task;
public async Task<int> AwaitAndGetResult()
{{
return await task.ConfigureAwait({(continueOnCapturedContext ? "true" : "false")});
}}
}}
";
var expected = this.CreateDiagnostic(10, 22, 21 + continueOnCapturedContext.ToString().Length);
await Verify.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task ReportWarningWhenConfiguredInlineTaskReturnedAwaitedFromMethod()
{
var test = @"
using System.Threading.Tasks;
using Microsoft.VisualStudio.Threading;
class Tests
{
private Task task;
public async Task AwaitAndGetResult()
{
await task.ConfigureAwaitRunInline();
}
}
";
var expected = this.CreateDiagnostic(11, 15, 30);
await Verify.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task ReportWarningWhenConfiguredInlineTaskTReturnedAwaitedFromMethod()
{
var test = @"
using System.Threading.Tasks;
using Microsoft.VisualStudio.Threading;
class Tests
{
private Task<int> task;
public async Task<int> AwaitAndGetResult()
{
return await task.ConfigureAwaitRunInline();
}
}
";
var expected = this.CreateDiagnostic(11, 22, 30);
await Verify.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task ReportWarningWhenTaskFromFieldIsAwaitedInJtfRunDelegate()
{
var test = @"
using System.Threading.Tasks;
using Microsoft.VisualStudio.Threading;
class Program
{
static Task t;
static JoinableTaskFactory jtf;
static void Main(string[] args)
{
jtf.Run(async delegate
{
await t;
});
}
}
";
var expected = this.CreateDiagnostic(14, 19, 1);
await Verify.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task ReportWarningWhenTaskTIsReturnedDirectlyWithCancellation()
{
@ -133,7 +284,30 @@ class Tests
}
[Fact]
public async Task DoNotReportWarningWhenTaskTIsPassedAsArgument()
public async Task DoNotReportWarningWhenTaskTIsPassedAsArgumentAndNoTaskIsReturned()
{
var test = @"
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Threading;
using Task = System.Threading.Tasks.Task;
class Tests
{
public static int WaitAndGetResult(Task task)
{
return DoSomethingWith(task);
}
private static int DoSomethingWith(Task t) => 3;
}
";
await Verify.VerifyAnalyzerAsync(test);
}
[Fact]
public async Task ReportWarningWhenTaskTIsPassedAsArgumentAndTaskIsReturned()
{
var test = @"
using System.Threading;
@ -152,7 +326,8 @@ class Tests
private static Task DoSomethingWith(Task t) => null;
}
";
await Verify.VerifyAnalyzerAsync(test);
var expected = this.CreateDiagnostic(12, 68, 4);
await Verify.VerifyAnalyzerAsync(test, expected);
}
[Fact]
@ -253,6 +428,44 @@ class Tests
await Verify.VerifyAnalyzerAsync(test);
}
[Fact]
public async Task DoNotReportWarningWhenReturnedTaskIsDirectlyReturnedFromInvocation()
{
var test = @"
using System.Threading.Tasks;
class Tests
{
public Task Test()
{
return SomeOperationAsync();
}
public Task SomeOperationAsync() => Task.CompletedTask;
}
";
await Verify.VerifyAnalyzerAsync(test);
}
[Fact]
public async Task DoNotReportWarningWhenReturnedTaskIsAwaitedReturnedFromInvocation()
{
var test = @"
using System.Threading.Tasks;
class Tests
{
public async Task<int> Test()
{
return await SomeOperationAsync();
}
public Task<int> SomeOperationAsync() => Task.FromResult(3);
}
";
await Verify.VerifyAnalyzerAsync(test);
}
[Fact]
public async Task DoNotReportWarningWhenTaskIsDefinedWithinDelegateInSubblock()
{
@ -698,53 +911,210 @@ class Tests
}
[Fact]
public async Task DoNotReportWarningForDerivedJoinableTaskFactoryWhenRunIsOverride()
public async Task ReportWarningWhenAwaitingTaskInField()
{
var test = @"
using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Threading;
using Task = System.Threading.Tasks.Task;
public class MyJoinableTaskFactory : JoinableTaskFactory
{
public MyJoinableTaskFactory(JoinableTaskFactory innerFactory) : base(innerFactory.Context)
{
}
new public void Run(Func<System.Threading.Tasks.Task> asyncMethod)
{
class Tests {
Task task;
public void Test() {
ThreadHelper.JoinableTaskFactory.Run(async delegate {
await task;
});
}
}
";
var expected = this.CreateDiagnostic(12, 19, 4);
await Verify.VerifyAnalyzerAsync(test, expected);
}
class Tests
{
public void Test()
{
MyJoinableTaskFactory myjtf = new MyJoinableTaskFactory(ThreadHelper.JoinableTaskFactory);
System.Threading.Tasks.Task<int> task = SomeOperationAsync();
myjtf.Run(async () =>
[Fact]
public async Task ReportWarningWhenAwaitingTaskInField_WithThisQualifier()
{
var test = @"
using System.Threading.Tasks;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Threading;
using Task = System.Threading.Tasks.Task;
class Tests {
Task task;
public void Test() {
ThreadHelper.JoinableTaskFactory.Run(async delegate {
await this.task;
});
}
}
";
var expected = this.CreateDiagnostic(12, 19, 9);
await Verify.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task DoNotReportWarningWhenAwaitingTaskInFieldThatIsAssignedLocally()
{
var test = @"
using System.Threading.Tasks;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Threading;
using Task = System.Threading.Tasks.Task;
class Tests {
Task task;
public void Test() {
ThreadHelper.JoinableTaskFactory.Run(async delegate {
task = SomeOperationAsync();
await task;
});
}
public async Task<int> SomeOperationAsync()
{
await System.Threading.Tasks.Task.Delay(1000);
return 100;
}
Task SomeOperationAsync() => Task.CompletedTask;
}
";
// We decided not to report warning in this case, because we don't know if our assumptions about the Run implementation are still valid for user's implementation
await Verify.VerifyAnalyzerAsync(test);
}
[Fact]
public async Task DoNotReportWarningWhenCompletedTaskIsReturnedDirectlyFromMethod()
{
var test = @"
using System.Threading.Tasks;
using Microsoft.VisualStudio.Threading;
class Tests
{
public Task GetTask()
{
return Task.CompletedTask;
}
public Task GetTask2()
{
return TplExtensions.CompletedTask;
}
}
";
await Verify.VerifyAnalyzerAsync(test);
}
[Fact]
public async Task DoNotReportWarningWhenTaskFromResultIsReturnedDirectlyFromMethod()
{
var test = @"
using System.Threading.Tasks;
class Tests
{
public Task<bool> GetTask()
{
return Task.FromResult(true);
}
}
";
await Verify.VerifyAnalyzerAsync(test);
}
[Fact]
public async Task DoNotReportWarningWhenTaskFromResultIsReturnedDirectlyFromMethod_FromField()
{
var test = @"
using System.Threading.Tasks;
class Tests
{
private static readonly Task CompletedTask = Task.FromResult(true);
public Task GetTask()
{
return CompletedTask;
}
}
";
await Verify.VerifyAnalyzerAsync(test);
}
[Fact]
public async Task ReportWarningWhenTaskFromResultIsReturnedDirectlyFromMethod_FromField_NotReadOnly()
{
var test = @"
using System.Threading.Tasks;
class Tests
{
private static Task CompletedTask = Task.FromResult(true); // this *could* be reassigned, and thus isn't safe
public Task GetTask()
{
return CompletedTask;
}
}
";
var expected = this.CreateDiagnostic(10, 16, 13);
await Verify.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task ReportWarningWhenTaskRunIsReturnedDirectlyFromMethod_FromField()
{
var test = @"
using System.Threading.Tasks;
class Tests
{
private static readonly Task SomeTask = Task.Run(() => true); // We're don't try to analyze the delegate
public Task GetTask()
{
return SomeTask;
}
}
";
var expected = this.CreateDiagnostic(10, 16, 8);
await Verify.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task TaskReturningMethodIncludeArgumentFromOtherSyntaxTree()
{
// This is a regression test for a bug that only repro'd when the field was defined in a different document from where it was used
// as input to a return value from a Task-returning method.
var source1 = @"
using System.Collections.Immutable;
using System.Threading.Tasks;
internal class Test
{
private Task<ImmutableHashSet<string>> SomethingAsync()
{
return Task.FromResult(OtherClass.ProjectSystem);
}
}
";
var source2 = @"
using System.Collections.Immutable;
class OtherClass
{
internal static readonly ImmutableHashSet<string> ProjectSystem =
ImmutableHashSet<string>.Empty.Union(new[]
{
""a""
});
}
";
var test = new Verify.Test { TestState = { Sources = { source1, source2 } } };
await test.RunAsync();
}
private DiagnosticResult CreateDiagnostic(int line, int column, int length) =>
Verify.Diagnostic().WithSpan(line, column, line, column + length);
}
}

Просмотреть файл

@ -18,6 +18,7 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
internal static class CommonInterest
{
internal static readonly Regex FileNamePatternForLegacyThreadSwitchingMembers = new Regex(@"^vs-threading\.LegacyThreadSwitchingMembers(\..*)?.txt$", FileNamePatternRegexOptions);
internal static readonly Regex FileNamePatternForMembersRequiringMainThread = new Regex(@"^vs-threading\.MembersRequiringMainThread(\..*)?.txt$", FileNamePatternRegexOptions);
internal static readonly Regex FileNamePatternForMethodsThatAssertMainThread = new Regex(@"^vs-threading\.MainThreadAssertingMethods(\..*)?.txt$", FileNamePatternRegexOptions);
internal static readonly Regex FileNamePatternForMethodsThatSwitchToMainThread = new Regex(@"^vs-threading\.MainThreadSwitchingMethods(\..*)?.txt$", FileNamePatternRegexOptions);
@ -41,18 +42,6 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.MicrosoftVisualStudioShellInterop, "IVsTask"), "GetResult"), extensionMethodNamespace: Namespaces.MicrosoftVisualStudioShell),
});
internal static readonly IEnumerable<QualifiedMember> LegacyThreadSwitchingMethods = new[]
{
new QualifiedMember(new QualifiedType(Namespaces.MicrosoftVisualStudioShell, Types.ThreadHelper.TypeName), Types.ThreadHelper.Invoke),
new QualifiedMember(new QualifiedType(Namespaces.MicrosoftVisualStudioShell, Types.ThreadHelper.TypeName), Types.ThreadHelper.InvokeAsync),
new QualifiedMember(new QualifiedType(Namespaces.MicrosoftVisualStudioShell, Types.ThreadHelper.TypeName), Types.ThreadHelper.BeginInvoke),
new QualifiedMember(new QualifiedType(Namespaces.SystemWindowsThreading, Types.Dispatcher.TypeName), Types.Dispatcher.Invoke),
new QualifiedMember(new QualifiedType(Namespaces.SystemWindowsThreading, Types.Dispatcher.TypeName), Types.Dispatcher.BeginInvoke),
new QualifiedMember(new QualifiedType(Namespaces.SystemWindowsThreading, Types.Dispatcher.TypeName), Types.Dispatcher.InvokeAsync),
new QualifiedMember(new QualifiedType(Namespaces.SystemThreading, Types.SynchronizationContext.TypeName), Types.SynchronizationContext.Send),
new QualifiedMember(new QualifiedType(Namespaces.SystemThreading, Types.SynchronizationContext.TypeName), Types.SynchronizationContext.Post),
};
internal static readonly IReadOnlyList<SyncBlockingMethod> SyncBlockingProperties = new[]
{
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemThreadingTasks, nameof(Task)), nameof(Task<int>.Result)), null),
@ -64,6 +53,10 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
new QualifiedMember(new QualifiedType(Namespaces.MicrosoftVisualStudioShell, Types.ThreadHelper.TypeName), Types.ThreadHelper.CheckAccess),
};
internal static readonly ImmutableArray<QualifiedMember> TaskConfigureAwait = ImmutableArray.Create(
new QualifiedMember(new QualifiedType(Types.Task.Namespace, Types.Task.TypeName), nameof(Task.ConfigureAwait)),
new QualifiedMember(new QualifiedType(Types.AwaitExtensions.Namespace, Types.AwaitExtensions.TypeName), Types.AwaitExtensions.ConfigureAwaitRunInline));
internal static readonly IImmutableSet<SyntaxKind> MethodSyntaxKinds = ImmutableHashSet.Create(
SyntaxKind.ConstructorDeclaration,
SyntaxKind.MethodDeclaration,

Просмотреть файл

@ -402,7 +402,7 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
{
// Is the FromResult method on the Task or Task<T> class?
var memberOwnerSymbol = semanticModel.GetSymbolInfo(originalSyntax, cancellationToken).Symbol;
if (memberOwnerSymbol?.ContainingType?.Name == nameof(Task) && memberOwnerSymbol.ContainingType.BelongsToNamespace(Namespaces.SystemThreadingTasks))
if (Utils.IsTask(memberOwnerSymbol?.ContainingType))
{
var simplified = awaitedInvocation.ArgumentList.Arguments.Single().Expression;
return simplified;

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="cs" original="MICROSOFT.VISUALSTUDIO.THREADING.ANALYZERS/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -58,14 +58,16 @@
<target state="translated">Použití InvokeAsync k vyvolání asynchronních událostí</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">Volání operátoru await u položky typu Task uvnitř JoinableTaskFactory.Run v případě, že je úloha inicializovaná mimo delegáta, může způsobit potenciální vzájemná blokování.
<source>Avoid awaiting or returning a Task representing work that was not started within your context as that can lead to deadlocks.
Start the work within this context, or use JoinableTaskFactory.RunAsync and await the returned JoinableTask instead.</source>
<target state="needs-review-translation">Volání operátoru await u položky typu Task uvnitř JoinableTaskFactory.Run v případě, že je úloha inicializovaná mimo delegáta, může způsobit potenciální vzájemná blokování.
Tomuto problému můžete předejít tak, že zajistíte inicializaci úlohy v rámci delegáta nebo místo položky typu Task použijete JoinableTask.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Vyhněte se použití operátoru await u nespojitelných úloh v kontextech spojení</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Vyhněte se použití operátoru await u nespojitelných úloh v kontextech spojení</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="de" original="MICROSOFT.VISUALSTUDIO.THREADING.ANALYZERS/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -58,14 +58,16 @@
<target state="translated">InvokeAsync zum Auslösen asynchroner Ereignisse verwenden</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">Das Aufrufen von "await" für eine Aufgabe innerhalb von "JoinableTaskFactory.Run" führt unter Umständen zu Deadlocks, wenn die Aufgabe außerhalb des Delegaten initialisiert wird.
Sie können dies vermeiden, indem Sie sicherstellen, dass die Aufgabe innerhalb des Delegaten initialisiert wird, oder "JoinableTask" anstelle der Aufgabe verwenden.</target>
<source>Avoid awaiting or returning a Task representing work that was not started within your context as that can lead to deadlocks.
Start the work within this context, or use JoinableTaskFactory.RunAsync and await the returned JoinableTask instead.</source>
<target state="needs-review-translation">Das Aufrufen von "await" für einen Task innerhalb von JoinableTaskFactory.Run führt unter Umständen zu Deadlocks, wenn der Task außerhalb des Delegaten initialisiert wird.
Sie können dies vermeiden, indem Sie sicherstellen, dass der Task innerhalb des Delegaten initialisiert wird, oder JoinableTask anstelle von Task verwenden.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Warten auf Tasks, für die kein Beitritt möglich ist, in Join-Kontexten vermeiden</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Warten auf Tasks, für die kein Beitritt möglich ist, in Join-Kontexten vermeiden</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="es" original="MICROSOFT.VISUALSTUDIO.THREADING.ANALYZERS/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -58,14 +58,16 @@
<target state="translated">Use InvokeAsync para desencadenar eventos asincrónicos</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">La llamada a await en un elemento Task dentro de JoinableTaskFactory.Run, cuando la tarea se ha iniciado fuera del delegado, puede provocar posibles interbloqueos.
<source>Avoid awaiting or returning a Task representing work that was not started within your context as that can lead to deadlocks.
Start the work within this context, or use JoinableTaskFactory.RunAsync and await the returned JoinableTask instead.</source>
<target state="needs-review-translation">La llamada a await en un elemento Task dentro de JoinableTaskFactory.Run, cuando la tarea se ha iniciado fuera del delegado, puede provocar potenciales interbloqueos.
Puede evitar este problema asegurándose de que la tarea se ha iniciado dentro del delegado o usando JoinableTask en lugar de Task.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Evite usar await para tareas que no sean de unión en contextos de unión</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Evite usar await para tareas que no sean de unión en contextos de unión</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="fr" original="MICROSOFT.VISUALSTUDIO.THREADING.ANALYZERS/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -58,14 +58,16 @@
<target state="translated">Utiliser InvokeAsync pour déclencher des événements async</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">Le fait d'appeler await sur une tâche dans une méthode JoinableTaskFactory.Run quand la tâche est initialisée en dehors du délégué peut entraîner des blocages.
<source>Avoid awaiting or returning a Task representing work that was not started within your context as that can lead to deadlocks.
Start the work within this context, or use JoinableTaskFactory.RunAsync and await the returned JoinableTask instead.</source>
<target state="needs-review-translation">Le fait dappeler await sur une tâche dans une méthode JoinableTaskFactory.Run quand la tâche est initialisée en dehors du délégué peut entraîner des blocages.
Vous pouvez éviter ce problème en vérifiant que la tâche est initialisée dans le délégué ou en utilisant JoinableTask à la place de Task.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Éviter dattendre des tâches non joignables dans des contextes de jointure</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Éviter dattendre des tâches non joignables dans des contextes de jointure</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="it" original="MICROSOFT.VISUALSTUDIO.THREADING.ANALYZERS/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -58,14 +58,16 @@
<target state="translated">Usa InvokeAsync per generare eventi asincroni</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">La chiamata di await su un elemento Task all'interno di un elemento JoinableTaskFactory.Run quando Task è inizializzato all'esterno del delegato può causare possibili deadlock.
<source>Avoid awaiting or returning a Task representing work that was not started within your context as that can lead to deadlocks.
Start the work within this context, or use JoinableTaskFactory.RunAsync and await the returned JoinableTask instead.</source>
<target state="needs-review-translation">La chiamata di await su un elemento Task all'interno di un elemento JoinableTaskFactory.Run quando Task è inizializzato all'esterno del delegato può causare possibili deadlock.
Per evitare il problema, assicurarsi che Task venga inizializzato all'interno del delegato oppure usare JoinableTask invece di Task.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Evita l'uso di await Task non joinable in contesti di join</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Evita attesa di Task non joinable in contesti di join</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="ja" original="MICROSOFT.VISUALSTUDIO.THREADING.ANALYZERS/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -58,14 +58,16 @@
<target state="translated">InvokeAsync を使用して非同期イベントを発生させる</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">タスクがデリゲート外部で初期化されている場合、JoinableTaskFactory.Run 内の Task で Await を呼び出すと、デッドロックが引き起こされる可能性があります。
<source>Avoid awaiting or returning a Task representing work that was not started within your context as that can lead to deadlocks.
Start the work within this context, or use JoinableTaskFactory.RunAsync and await the returned JoinableTask instead.</source>
<target state="needs-review-translation">タスクがデリゲート外部で初期化されている場合、JoinableTaskFactory.Run 内の Task で Await を呼び出すと、デッドロックが引き起こされる可能性があります。
この問題は、タスクが確実にデリゲート内で初期化されるようにするか、Task の代わりに JoinableTask を使用することによって回避できます。</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">結合コンテキストで結合できないタスクを待機しない</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">結合コンテキストで結合できないタスクを待機しない</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="ko" original="MICROSOFT.VISUALSTUDIO.THREADING.ANALYZERS/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -58,14 +58,16 @@
<target state="translated">비동기 이벤트를 발생하는 InvokeAsync 사용</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">작업이 외부에서 초기화되면 대리자가 잠재적인 교착 상태를 일으킬 수 있으므로 JoinableTaskFactory.Run 내 Task에서 호출을 대기합니다.
<source>Avoid awaiting or returning a Task representing work that was not started within your context as that can lead to deadlocks.
Start the work within this context, or use JoinableTaskFactory.RunAsync and await the returned JoinableTask instead.</source>
<target state="needs-review-translation">작업이 외부에서 초기화되면 대리자가 잠재적인 교착 상태를 일으킬 수 있으므로 JoinableTaskFactory.Run 내 Task에서 호출을 대기합니다.
작업이 대리자 내에서 초기화되도록 하거나 Task 대신 JoinableTask를 사용하여 이 문제를 방지할 수 있습니다.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">조인 컨텍스트에서 조인할 수 없는 작업을 대기하지 않습니다.</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">조인 컨텍스트에서 조인할 수 없는 작업을 대기하지 않습니다.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="pl" original="MICROSOFT.VISUALSTUDIO.THREADING.ANALYZERS/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -58,14 +58,16 @@
<target state="translated">Zdarzenia asynchroniczne wywołuj za pomocą metody InvokeAsync</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">Wywołanie oczekiwania dla obiektu Task wewnątrz metody JoinableTaskFactory.Run, gdy dane zadanie zainicjowano poza delegatem, może spowodować zakleszczenia.
<source>Avoid awaiting or returning a Task representing work that was not started within your context as that can lead to deadlocks.
Start the work within this context, or use JoinableTaskFactory.RunAsync and await the returned JoinableTask instead.</source>
<target state="needs-review-translation">Wywołanie oczekiwania dla obiektu Task wewnątrz metody JoinableTaskFactory.Run, gdy dane zadanie zainicjowano poza delegatem, może spowodować zakleszczenia.
Tego problemu można uniknąć, zapewniając, że zadanie zostanie zainicjowane wewnątrz delegata lub za pomocą klasy JoinableTask (a nie klasy Task).</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Unikaj oczekiwania na zadania bez możliwości dołączenia w kontekstach dołączania</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Unikaj oczekiwania na zadania bez możliwości dołączenia w kontekstach dołączania</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="pt-BR" original="MICROSOFT.VISUALSTUDIO.THREADING.ANALYZERS/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -58,14 +58,16 @@
<target state="translated">Use InvokeAsync para acionar eventos assíncronos</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">Chamar await em uma Task em JoinableTaskFactory.Run, quando a tarefa for inicializada fora do delegado pode causar deadlocks em potencial.
<source>Avoid awaiting or returning a Task representing work that was not started within your context as that can lead to deadlocks.
Start the work within this context, or use JoinableTaskFactory.RunAsync and await the returned JoinableTask instead.</source>
<target state="needs-review-translation">Chamar await em uma Task em JoinableTaskFactory.Run, quando a tarefa for inicializada fora do delegado pode causar deadlocks em potencial.
Você pode evitar esse problema assegurando que a tarefa seja inicializada no delegado ou usando JoinableTask em vez de Task.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Evite aguardar tarefas não unidas em contextos de junção</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Evite aguardar tarefas não unidas em contextos de junção</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="ru" original="MICROSOFT.VISUALSTUDIO.THREADING.ANALYZERS/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -58,14 +58,16 @@
<target state="translated">Использование InvokeAsync для вызова асинхронных событий</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">При вызове await в объекте Task в JoinableTaskFactory.Run (и при условии, что задача инициализирована за пределами делегата) могут произойти взаимоблокировки.
<source>Avoid awaiting or returning a Task representing work that was not started within your context as that can lead to deadlocks.
Start the work within this context, or use JoinableTaskFactory.RunAsync and await the returned JoinableTask instead.</source>
<target state="needs-review-translation">При вызове await в объекте Task в JoinableTaskFactory.Run (и при условии, что задача инициализирована за пределами делегата), могут произойти взаимоблокировки.
Вы можете избежать этой проблемы, инициализировав задачу в делегате или использовав JoinableTask вместо Task.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Избегание await в неприсоединяемых задачах в контекстах соединения</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Избегание await в неприсоединяемых задачах в контекстах соединения</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="tr" original="MICROSOFT.VISUALSTUDIO.THREADING.ANALYZERS/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -58,14 +58,16 @@
<target state="translated">Async olayları tetiklemek için InvokeAsync kullanın</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">JoinableTaskFactory.Run içindeki bir Task için await çağırmak, görevin temsilci dışında başlatılması durumunda olası kilitlenmelere neden olabilir.
Görevin temsilci içinde başlatılmasını sağlayarak veya Task yerine JoinableTask kullanarak bu sorunu önleyebilirsiniz.</target>
<source>Avoid awaiting or returning a Task representing work that was not started within your context as that can lead to deadlocks.
Start the work within this context, or use JoinableTaskFactory.RunAsync and await the returned JoinableTask instead.</source>
<target state="needs-review-translation">JoinableTaskFactory.Run içindeki bir Task için await çağırmak, görevin temsilci dışında başlatılması durumunda kilitlenmelere neden olabilir.
Görevin temsilcinin içinde başlatılmasını sağlayarak veya Task yerine JoinableTask kullanarak bu sorunu önleyebilirsiniz.</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">Birleşik olmayan görevleri birleşik ortamlarda beklemeyin</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">Birleşik olmayan görevleri birleşik ortamlarda beklemeyin</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="zh-Hans" original="MICROSOFT.VISUALSTUDIO.THREADING.ANALYZERS/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -58,14 +58,16 @@
<target state="translated">使用 InvokeAsync 引发异步事件</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">在委托外初始化任务时,对 JoinableTaskFactory.Run 内部 Task 调用 await 可能导致死锁。
<source>Avoid awaiting or returning a Task representing work that was not started within your context as that can lead to deadlocks.
Start the work within this context, or use JoinableTaskFactory.RunAsync and await the returned JoinableTask instead.</source>
<target state="needs-review-translation">在委托外初始化任务时,对 JoinableTaskFactory.Run 内部 Task 调用 await 可能导致死锁。
通过确保在委托内部初始化任务或使用 JoinableTask 取代 Task可以避免此问题。</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">避免等待联接上下文中的非可加入任务</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">避免等待联接上下文中的非可加入任务</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="zh-Hant" original="MICROSOFT.VISUALSTUDIO.THREADING.ANALYZERS/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -58,14 +58,16 @@
<target state="translated">使用 InvokeAsync 引發非同步事件</target>
</trans-unit>
<trans-unit id="VSTHRD003_MessageFormat" translate="yes" xml:space="preserve">
<source>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</source>
<target state="translated">當工作在委派的外部初始化時,對 JoinableTaskFactory.Run 內的 Task 上呼叫 await 可能會造成潛在的死結。
您可以讓工作在委派內初始化,或不使用 Task 而改用 JoinableTask 來避免此問題。</target>
<source>Avoid awaiting or returning a Task representing work that was not started within your context as that can lead to deadlocks.
Start the work within this context, or use JoinableTaskFactory.RunAsync and await the returned JoinableTask instead.</source>
<target state="needs-review-translation">當工作在委派外部初始化時,在 JoinableTaskFactory.Run 內的 Task 上呼叫 await可能會造成潛在的鎖死情形。
您可以藉由確認工作在委派內初始化,或將 Task 改為使用 JoinableTask來避免此問題。</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD003_Title" translate="yes" xml:space="preserve">
<source>Avoid awaiting non-joinable tasks in join contexts</source>
<target state="translated">避免在加入內容中等候不可加入的工作</target>
<source>Avoid awaiting foreign Tasks</source>
<target state="needs-review-translation">避免在加入內容中等候不可加入的工作</target>
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translations accuracy as the source string was updated after it was translated.</note>
</trans-unit>
<trans-unit id="VSTHRD011_MessageFormat" translate="yes" xml:space="preserve">
<source>Lazy&lt;Task&lt;T&gt;&gt;.Value can deadlock.

Просмотреть файл

@ -125,8 +125,8 @@ namespace Microsoft.VisualStudio.Threading.Analyzers {
}
/// <summary>
/// Looks up a localized string similar to Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
///You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task..
/// Looks up a localized string similar to Avoid awaiting or returning a Task representing work that was not started within your context as that can lead to deadlocks.
///Start the work within this context, or use JoinableTaskFactory.RunAsync and await the returned JoinableTask instead..
/// </summary>
internal static string VSTHRD003_MessageFormat {
get {
@ -135,7 +135,7 @@ namespace Microsoft.VisualStudio.Threading.Analyzers {
}
/// <summary>
/// Looks up a localized string similar to Avoid awaiting non-joinable tasks in join contexts.
/// Looks up a localized string similar to Avoid awaiting foreign Tasks.
/// </summary>
internal static string VSTHRD003_Title {
get {

Просмотреть файл

@ -53,8 +53,8 @@
<value>InvokeAsync zum Auslösen asynchroner Ereignisse verwenden</value>
</data>
<data name="VSTHRD003_MessageFormat" xml:space="preserve">
<value>Das Aufrufen von "await" für eine Aufgabe innerhalb von "JoinableTaskFactory.Run" führt unter Umständen zu Deadlocks, wenn die Aufgabe außerhalb des Delegaten initialisiert wird.
Sie können dies vermeiden, indem Sie sicherstellen, dass die Aufgabe innerhalb des Delegaten initialisiert wird, oder "JoinableTask" anstelle der Aufgabe verwenden.</value>
<value>Das Aufrufen von "await" für einen Task innerhalb von JoinableTaskFactory.Run führt unter Umständen zu Deadlocks, wenn der Task außerhalb des Delegaten initialisiert wird.
Sie können dies vermeiden, indem Sie sicherstellen, dass der Task innerhalb des Delegaten initialisiert wird, oder JoinableTask anstelle von Task verwenden.</value>
</data>
<data name="VSTHRD003_Title" xml:space="preserve">
<value>Warten auf Tasks, für die kein Beitritt möglich ist, in Join-Kontexten vermeiden</value>

Просмотреть файл

@ -53,7 +53,7 @@
<value>Use InvokeAsync para desencadenar eventos asincrónicos</value>
</data>
<data name="VSTHRD003_MessageFormat" xml:space="preserve">
<value>La llamada a await en un elemento Task dentro de JoinableTaskFactory.Run, cuando la tarea se ha iniciado fuera del delegado, puede provocar posibles interbloqueos.
<value>La llamada a await en un elemento Task dentro de JoinableTaskFactory.Run, cuando la tarea se ha iniciado fuera del delegado, puede provocar potenciales interbloqueos.
Puede evitar este problema asegurándose de que la tarea se ha iniciado dentro del delegado o usando JoinableTask en lugar de Task.</value>
</data>
<data name="VSTHRD003_Title" xml:space="preserve">

Просмотреть файл

@ -53,7 +53,7 @@
<value>Utiliser InvokeAsync pour déclencher des événements async</value>
</data>
<data name="VSTHRD003_MessageFormat" xml:space="preserve">
<value>Le fait d'appeler await sur une tâche dans une méthode JoinableTaskFactory.Run quand la tâche est initialisée en dehors du délégué peut entraîner des blocages.
<value>Le fait dappeler await sur une tâche dans une méthode JoinableTaskFactory.Run quand la tâche est initialisée en dehors du délégué peut entraîner des blocages.
Vous pouvez éviter ce problème en vérifiant que la tâche est initialisée dans le délégué ou en utilisant JoinableTask à la place de Task.</value>
</data>
<data name="VSTHRD003_Title" xml:space="preserve">

Просмотреть файл

@ -57,7 +57,7 @@
Per evitare il problema, assicurarsi che Task venga inizializzato all'interno del delegato oppure usare JoinableTask invece di Task.</value>
</data>
<data name="VSTHRD003_Title" xml:space="preserve">
<value>Evita l'uso di await Task non joinable in contesti di join</value>
<value>Evita attesa di Task non joinable in contesti di join</value>
</data>
<data name="VSTHRD011_MessageFormat" xml:space="preserve">
<value>Lazy&lt;Task&lt;T&gt;&gt;.Value può causare deadlock.

Просмотреть файл

@ -166,11 +166,11 @@
<value>Use InvokeAsync to raise async events</value>
</data>
<data name="VSTHRD003_MessageFormat" xml:space="preserve">
<value>Calling await on a Task inside a JoinableTaskFactory.Run, when the task is initialized outside the delegate can cause potential deadlocks.
You can avoid this problem by ensuring the task is initialized within the delegate or by using JoinableTask instead of Task.</value>
<value>Avoid awaiting or returning a Task representing work that was not started within your context as that can lead to deadlocks.
Start the work within this context, or use JoinableTaskFactory.RunAsync and await the returned JoinableTask instead.</value>
</data>
<data name="VSTHRD003_Title" xml:space="preserve">
<value>Avoid awaiting non-joinable tasks in join contexts</value>
<value>Avoid awaiting foreign Tasks</value>
</data>
<data name="VSTHRD011b_MessageFormat" xml:space="preserve">
<value>Invoking or blocking on async code in a Lazy&lt;T&gt; value factory can deadlock.

Просмотреть файл

@ -53,7 +53,7 @@
<value>Использование InvokeAsync для вызова асинхронных событий</value>
</data>
<data name="VSTHRD003_MessageFormat" xml:space="preserve">
<value>При вызове await в объекте Task в JoinableTaskFactory.Run (и при условии, что задача инициализирована за пределами делегата) могут произойти взаимоблокировки.
<value>При вызове await в объекте Task в JoinableTaskFactory.Run (и при условии, что задача инициализирована за пределами делегата), могут произойти взаимоблокировки.
Вы можете избежать этой проблемы, инициализировав задачу в делегате или использовав JoinableTask вместо Task.</value>
</data>
<data name="VSTHRD003_Title" xml:space="preserve">

Просмотреть файл

@ -53,8 +53,8 @@
<value>Async olayları tetiklemek için InvokeAsync kullanın</value>
</data>
<data name="VSTHRD003_MessageFormat" xml:space="preserve">
<value>JoinableTaskFactory.Run içindeki bir Task için await çağırmak, görevin temsilci dışında başlatılması durumunda olası kilitlenmelere neden olabilir.
Görevin temsilci içinde başlatılmasını sağlayarak veya Task yerine JoinableTask kullanarak bu sorunu önleyebilirsiniz.</value>
<value>JoinableTaskFactory.Run içindeki bir Task için await çağırmak, görevin temsilci dışında başlatılması durumunda kilitlenmelere neden olabilir.
Görevin temsilcinin içinde başlatılmasını sağlayarak veya Task yerine JoinableTask kullanarak bu sorunu önleyebilirsiniz.</value>
</data>
<data name="VSTHRD003_Title" xml:space="preserve">
<value>Birleşik olmayan görevleri birleşik ortamlarda beklemeyin</value>

Просмотреть файл

@ -53,8 +53,8 @@
<value>使用 InvokeAsync 引發非同步事件</value>
</data>
<data name="VSTHRD003_MessageFormat" xml:space="preserve">
<value>當工作在委派的外部初始化時,對 JoinableTaskFactory.Run 內的 Task 上呼叫 await 可能會造成潛在的死結
您可以讓工作在委派內初始化,或不使用 Task 而改用 JoinableTask 來避免此問題。</value>
<value>當工作在委派外部初始化時,在 JoinableTaskFactory.Run 內的 Task 上呼叫 await可能會造成潛在的鎖死情形
您可以藉由確認工作在委派內初始化,或將 Task 改為使用 JoinableTask來避免此問題。</value>
</data>
<data name="VSTHRD003_Title" xml:space="preserve">
<value>避免在加入內容中等候不可加入的工作</value>

Просмотреть файл

@ -14,6 +14,21 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
/// </summary>
internal static class Types
{
internal static class AwaitExtensions
{
/// <summary>
/// The full name of the AwaitExtensions type.
/// </summary>
internal const string TypeName = "AwaitExtensions";
/// <summary>
/// The name of the ConfigureAwaitRunInline method.
/// </summary>
internal const string ConfigureAwaitRunInline = "ConfigureAwaitRunInline";
internal static readonly IReadOnlyList<string> Namespace = Namespaces.MicrosoftVisualStudioThreading;
}
/// <summary>
/// Contains the names of types and members within TplExtensions.
/// </summary>
@ -29,6 +44,11 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
/// </summary>
internal const string InvokeAsync = "InvokeAsync";
/// <summary>
/// The name of the CompletedTask field.
/// </summary>
internal const string CompletedTask = "CompletedTask";
internal static readonly IReadOnlyList<string> Namespace = Namespaces.MicrosoftVisualStudioThreading;
}

Просмотреть файл

@ -4,9 +4,6 @@
* *
*********************************************************/
// https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2267
#pragma warning disable SA1009 // Closing parenthesis must be spaced correctly
namespace Microsoft.VisualStudio.Threading.Analyzers
{
using System;
@ -67,6 +64,25 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
};
}
internal static Action<CodeBlockAnalysisContext> DebuggableWrapper(Action<CodeBlockAnalysisContext> handler)
{
return ctxt =>
{
try
{
handler(ctxt);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex) when (LaunchDebuggerExceptionFilter())
{
throw new Exception($"Analyzer failure while processing syntax at {ctxt.CodeBlock.SyntaxTree.FilePath}({ctxt.CodeBlock.GetLocation()?.GetLineSpan().StartLinePosition.Line + 1},{ctxt.CodeBlock.GetLocation()?.GetLineSpan().StartLinePosition.Character + 1}): {ex.GetType()} {ex.Message}. Syntax: {ctxt.CodeBlock}", ex);
}
};
}
internal static ExpressionSyntax IsolateMethodName(InvocationExpressionSyntax invocation)
{
if (invocation == null)
@ -236,6 +252,8 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
|| typeSymbol.GetAttributes().Any(ad => ad.AttributeClass?.Name == Types.AsyncMethodBuilderAttribute.TypeName && ad.AttributeClass.BelongsToNamespace(Types.AsyncMethodBuilderAttribute.Namespace));
}
internal static bool IsTask(ITypeSymbol typeSymbol) => typeSymbol?.Name == nameof(Task) && typeSymbol.BelongsToNamespace(Namespaces.SystemThreadingTasks);
/// <summary>
/// Gets a value indicating whether a method is async or is ready to be async by having an async-compatible return type.
/// </summary>
@ -371,6 +389,38 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
return false;
}
internal static bool IsAssignedWithin(SyntaxNode container, SemanticModel semanticModel, ISymbol variable, CancellationToken cancellationToken)
{
if (semanticModel == null)
{
throw new ArgumentNullException(nameof(semanticModel));
}
if (variable == null)
{
throw new ArgumentNullException(nameof(variable));
}
if (container == null)
{
return false;
}
foreach (var node in container.DescendantNodesAndSelf(n => !(n is AnonymousFunctionExpressionSyntax)))
{
if (node is AssignmentExpressionSyntax assignment)
{
var assignedSymbol = semanticModel.GetSymbolInfo(assignment.Left, cancellationToken).Symbol;
if (variable.Equals(assignedSymbol))
{
return true;
}
}
}
return false;
}
internal static IEnumerable<ITypeSymbol> FindInterfacesImplemented(this ISymbol symbol)
{
if (symbol == null)

Просмотреть файл

@ -1,11 +1,6 @@
namespace Microsoft.VisualStudio.Threading.Analyzers
{
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CodeAnalysis;
using CodeAnalysis.CSharp;
using CodeAnalysis.CSharp.Syntax;
@ -32,26 +27,41 @@
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
context.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(this.AnalyzeInvocation), SyntaxKind.InvocationExpression);
context.RegisterCompilationStartAction(compilationStartContext =>
{
var legacyThreadSwitchingMembers = CommonInterest.ReadMethods(compilationStartContext.Options, CommonInterest.FileNamePatternForLegacyThreadSwitchingMembers, compilationStartContext.CancellationToken).ToImmutableArray();
var analyzer = new Analyzer(legacyThreadSwitchingMembers);
compilationStartContext.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(analyzer.AnalyzeInvocation), SyntaxKind.InvocationExpression);
});
}
private void AnalyzeInvocation(SyntaxNodeAnalysisContext context)
private class Analyzer
{
var invocationSyntax = (InvocationExpressionSyntax)context.Node;
var invokeMethod = context.SemanticModel.GetSymbolInfo(context.Node).Symbol as IMethodSymbol;
if (invokeMethod != null)
{
foreach (var legacyMethod in CommonInterest.LegacyThreadSwitchingMethods)
{
context.CancellationToken.ThrowIfCancellationRequested();
private readonly ImmutableArray<CommonInterest.QualifiedMember> legacyThreadSwitchingMembers;
if (legacyMethod.IsMatch(invokeMethod))
internal Analyzer(ImmutableArray<CommonInterest.QualifiedMember> legacyThreadSwitchingMembers)
{
this.legacyThreadSwitchingMembers = legacyThreadSwitchingMembers;
}
internal void AnalyzeInvocation(SyntaxNodeAnalysisContext context)
{
var invocationSyntax = (InvocationExpressionSyntax)context.Node;
var invokeMethod = context.SemanticModel.GetSymbolInfo(context.Node).Symbol as IMethodSymbol;
if (invokeMethod != null)
{
foreach (var legacyMethod in this.legacyThreadSwitchingMembers)
{
var diagnostic = Diagnostic.Create(
Descriptor,
invocationSyntax.Expression.GetLocation());
context.ReportDiagnostic(diagnostic);
break;
context.CancellationToken.ThrowIfCancellationRequested();
if (legacyMethod.IsMatch(invokeMethod))
{
var diagnostic = Diagnostic.Create(
Descriptor,
invocationSyntax.Expression.GetLocation());
context.ReportDiagnostic(diagnostic);
break;
}
}
}
}

Просмотреть файл

@ -51,6 +51,11 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);
private static readonly IReadOnlyCollection<Type> DoNotPassTypesInSearchForAnonFuncInvocation = new[]
{
typeof(MethodDeclarationSyntax),
};
/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
@ -67,223 +72,178 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
context.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(this.AnalyzeAwaitExpression), SyntaxKind.AwaitExpression);
context.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(this.AnalyzeReturnStatement), SyntaxKind.ReturnStatement);
context.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(this.AnalyzeLambdaExpression), SyntaxKind.SimpleLambdaExpression);
context.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(this.AnalyzeLambdaExpression), SyntaxKind.ParenthesizedLambdaExpression);
}
private void AnalyzeLambdaExpression(SyntaxNodeAnalysisContext context)
{
var anonFuncSyntax = (AnonymousFunctionExpressionSyntax)context.Node;
// Check whether it is called by Jtf.Run
InvocationExpressionSyntax invocationExpressionSyntax = this.FindInvocationOfDelegateOrLambdaExpression(anonFuncSyntax);
if (invocationExpressionSyntax == null || !this.IsInvocationExpressionACallToJtfRun(context, invocationExpressionSyntax))
var lambdaExpression = (LambdaExpressionSyntax)context.Node;
if (lambdaExpression.Body is ExpressionSyntax expression)
{
return;
var diagnostic = this.AnalyzeAwaitedOrReturnedExpression(expression, context, context.CancellationToken);
if (diagnostic != null)
{
context.ReportDiagnostic(diagnostic);
}
}
}
var expressionsToSearch = Enumerable.Empty<ExpressionSyntax>();
switch (anonFuncSyntax.Body)
private void AnalyzeReturnStatement(SyntaxNodeAnalysisContext context)
{
var returnStatement = (ReturnStatementSyntax)context.Node;
var diagnostic = this.AnalyzeAwaitedOrReturnedExpression(returnStatement.Expression, context, context.CancellationToken);
if (diagnostic != null)
{
case ExpressionSyntax expr:
expressionsToSearch = new ExpressionSyntax[] { expr };
break;
case BlockSyntax block:
expressionsToSearch = from ret in block.DescendantNodes().OfType<ReturnStatementSyntax>()
where ret.Expression != null
select ret.Expression;
break;
}
var identifiers = from ex in expressionsToSearch
from identifier in ex.DescendantNodesAndSelf(n => !(n is ArgumentListSyntax)).OfType<IdentifierNameSyntax>()
select new { expression = ex, identifier };
foreach (var identifier in identifiers)
{
this.ReportDiagnosticIfSymbolIsExternalTask(context, identifier.expression, identifier.identifier, anonFuncSyntax);
context.ReportDiagnostic(diagnostic);
}
}
private void AnalyzeAwaitExpression(SyntaxNodeAnalysisContext context)
{
AwaitExpressionSyntax awaitExpressionSyntax = (AwaitExpressionSyntax)context.Node;
IdentifierNameSyntax identifierNameSyntaxAwaitingOn = awaitExpressionSyntax.Expression as IdentifierNameSyntax;
if (identifierNameSyntaxAwaitingOn == null)
var diagnostic = this.AnalyzeAwaitedOrReturnedExpression(awaitExpressionSyntax.Expression, context, context.CancellationToken);
if (diagnostic != null)
{
return;
context.ReportDiagnostic(diagnostic);
}
SyntaxNode currentNode = identifierNameSyntaxAwaitingOn;
// Step 1: Find the async delegate or lambda expression that matches the await
SyntaxNode delegateOrLambdaNode = this.FindAsyncDelegateOrLambdaExpressiomMatchingAwait(awaitExpressionSyntax);
if (delegateOrLambdaNode == null)
{
return;
}
// Step 2: Check whether it is called by Jtf.Run
InvocationExpressionSyntax invocationExpressionSyntax = this.FindInvocationOfDelegateOrLambdaExpression(delegateOrLambdaNode);
if (invocationExpressionSyntax == null || !this.IsInvocationExpressionACallToJtfRun(context, invocationExpressionSyntax))
{
return;
}
this.ReportDiagnosticIfSymbolIsExternalTask(context, awaitExpressionSyntax.Expression, identifierNameSyntaxAwaitingOn, delegateOrLambdaNode);
}
private void ReportDiagnosticIfSymbolIsExternalTask(SyntaxNodeAnalysisContext context, ExpressionSyntax expressionSyntax, IdentifierNameSyntax identifierNameToConsider, SyntaxNode delegateOrLambdaNode)
private Diagnostic AnalyzeAwaitedOrReturnedExpression(ExpressionSyntax expressionSyntax, SyntaxNodeAnalysisContext context, CancellationToken cancellationToken)
{
// Step 3: Is the symbol we are waiting on a System.Threading.Tasks.Task
SymbolInfo symbolToConsider = context.SemanticModel.GetSymbolInfo(identifierNameToConsider);
if (expressionSyntax == null)
{
return null;
}
// Get the semantic model for the SyntaxTree for the given ExpressionSyntax, since it *may* not be in the same syntax tree
// as the original context.Node.
var semanticModel = context.Compilation.GetSemanticModel(expressionSyntax.SyntaxTree);
SymbolInfo symbolToConsider = semanticModel.GetSymbolInfo(expressionSyntax, cancellationToken);
if (CommonInterest.TaskConfigureAwait.Any(configureAwait => configureAwait.IsMatch(symbolToConsider.Symbol)))
{
if (((InvocationExpressionSyntax)expressionSyntax).Expression is MemberAccessExpressionSyntax memberAccessExpression)
{
symbolToConsider = semanticModel.GetSymbolInfo(memberAccessExpression.Expression, cancellationToken);
}
}
ITypeSymbol symbolType;
bool dataflowAnalysisCompatibleVariable = false;
switch (symbolToConsider.Symbol)
{
case ILocalSymbol localSymbol:
symbolType = localSymbol.Type;
dataflowAnalysisCompatibleVariable = true;
break;
case IParameterSymbol parameterSymbol:
symbolType = parameterSymbol.Type;
dataflowAnalysisCompatibleVariable = true;
break;
case IFieldSymbol fieldSymbol:
symbolType = fieldSymbol.Type;
// If the field is readonly and initialized with Task.FromResult, it's OK.
if (fieldSymbol.IsReadOnly)
{
// Whitelist the TplExtensions.CompletedTask field.
if (fieldSymbol.Name == Types.TplExtensions.CompletedTask && fieldSymbol.ContainingType.Name == Types.TplExtensions.TypeName && fieldSymbol.BelongsToNamespace(Types.TplExtensions.Namespace))
{
return null;
}
// If we can find the source code for the field, we can check whether it has a field initializer
// that stores the result of a Task.FromResult invocation.
foreach (var syntaxReference in fieldSymbol.DeclaringSyntaxReferences)
{
if (syntaxReference.GetSyntax(cancellationToken) is VariableDeclaratorSyntax declarationSyntax &&
declarationSyntax.Initializer?.Value is InvocationExpressionSyntax invocationSyntax &&
invocationSyntax.Expression != null)
{
var declarationSemanticModel = context.Compilation.GetSemanticModel(invocationSyntax.SyntaxTree);
if (declarationSemanticModel.GetSymbolInfo(invocationSyntax.Expression, cancellationToken).Symbol is IMethodSymbol invokedMethod &&
invokedMethod.Name == nameof(Task.FromResult) &&
invokedMethod.ContainingType.Name == nameof(Task) &&
invokedMethod.ContainingType.BelongsToNamespace(Types.Task.Namespace))
{
return null;
}
}
}
}
break;
case IMethodSymbol methodSymbol:
if (Utils.IsTask(methodSymbol.ReturnType) && expressionSyntax is InvocationExpressionSyntax invocationExpressionSyntax)
{
// Consider all arguments
var expressionsToConsider = invocationExpressionSyntax.ArgumentList.Arguments.Select(a => a.Expression);
// Consider the implicit first argument when this method is invoked as an extension method.
if (methodSymbol.IsExtensionMethod && invocationExpressionSyntax.Expression is MemberAccessExpressionSyntax invokedMember)
{
if (!methodSymbol.ContainingType.Equals(semanticModel.GetSymbolInfo(invokedMember.Expression, cancellationToken).Symbol))
{
expressionsToConsider = new ExpressionSyntax[] { invokedMember.Expression }.Concat(expressionsToConsider);
}
}
return expressionsToConsider.Select(e => this.AnalyzeAwaitedOrReturnedExpression(e, context, cancellationToken)).FirstOrDefault(r => r != null);
}
return null;
default:
return;
return null;
}
if (symbolType == null || symbolType.Name != nameof(Task) || !symbolType.BelongsToNamespace(Namespaces.SystemThreadingTasks))
if (symbolType?.Name != nameof(Task) || !symbolType.BelongsToNamespace(Namespaces.SystemThreadingTasks))
{
return;
return null;
}
// Step 4: Report warning if the task was not initialized within the current delegate or lambda expression
CSharpSyntaxNode delegateBlockOrBlock = this.GetBlockOrExpressionBodyOfDelegateOrLambdaExpression(delegateOrLambdaNode);
if (delegateBlockOrBlock is BlockSyntax delegateBlock)
// Report warning if the task was not initialized within the current delegate or lambda expression
var containingFunc = Utils.GetContainingFunction(expressionSyntax);
if (containingFunc.BlockOrExpression is BlockSyntax delegateBlock)
{
// Run data flow analysis to understand where the task was defined
DataFlowAnalysis dataFlowAnalysis;
// When possible (await is direct child of the block), execute data flow analysis by passing first and last statement to capture only what happens before the await
// Check if the await is direct child of the code block (first parent is ExpressionStantement, second parent is the block itself)
if (expressionSyntax.Parent.Parent.Parent.Equals(delegateBlock))
if (dataflowAnalysisCompatibleVariable)
{
dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(delegateBlock.ChildNodes().First(), expressionSyntax.Parent.Parent);
// Run data flow analysis to understand where the task was defined
DataFlowAnalysis dataFlowAnalysis;
// When possible (await is direct child of the block and not a field), execute data flow analysis by passing first and last statement to capture only what happens before the await
// Check if the await is direct child of the code block (first parent is ExpressionStantement, second parent is the block itself)
if (delegateBlock.Equals(expressionSyntax.Parent.Parent?.Parent))
{
dataFlowAnalysis = semanticModel.AnalyzeDataFlow(delegateBlock.ChildNodes().First(), expressionSyntax.Parent.Parent);
}
else
{
// Otherwise analyze the data flow for the entire block. One caveat: it doesn't distinguish if the initalization happens after the await.
dataFlowAnalysis = semanticModel.AnalyzeDataFlow(delegateBlock);
}
if (!dataFlowAnalysis.WrittenInside.Contains(symbolToConsider.Symbol))
{
return Diagnostic.Create(Descriptor, expressionSyntax.GetLocation());
}
}
else
{
// Otherwise analyze the data flow for the entire block. One caveat: it doesn't distinguish if the initalization happens after the await.
dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(delegateBlock);
}
if (!dataFlowAnalysis.WrittenInside.Contains(symbolToConsider.Symbol))
{
context.ReportDiagnostic(Diagnostic.Create(Descriptor, expressionSyntax.GetLocation()));
// Do the best we can searching for assignment statements.
if (!Utils.IsAssignedWithin(containingFunc.BlockOrExpression, semanticModel, symbolToConsider.Symbol, cancellationToken))
{
return Diagnostic.Create(Descriptor, expressionSyntax.GetLocation());
}
}
}
else
{
// It's not a block, it's just a lambda expression, so the variable must be external.
context.ReportDiagnostic(Diagnostic.Create(Descriptor, expressionSyntax.GetLocation()));
}
}
/// <summary>
/// Finds the async delegate or lambda expression that matches the await by walking up the syntax tree until we encounter an async delegate or lambda expression.
/// </summary>
/// <param name="awaitExpressionSyntax">The await expression syntax.</param>
/// <returns>Node representing the delegate or lambda expression if found. Null if not found.</returns>
private SyntaxNode FindAsyncDelegateOrLambdaExpressiomMatchingAwait(AwaitExpressionSyntax awaitExpressionSyntax)
{
SyntaxNode currentNode = awaitExpressionSyntax;
while (currentNode != null && !(currentNode is MethodDeclarationSyntax))
{
AnonymousMethodExpressionSyntax anonymousMethod = currentNode as AnonymousMethodExpressionSyntax;
if (anonymousMethod != null && anonymousMethod.AsyncKeyword != null)
{
return currentNode;
}
if ((currentNode as ParenthesizedLambdaExpressionSyntax)?.AsyncKeyword != null)
{
return currentNode;
}
// Advance to the next parent
currentNode = currentNode.Parent;
return Diagnostic.Create(Descriptor, expressionSyntax.GetLocation());
}
return null;
}
/// <summary>
/// Helper method to get the code Block of a delegate or lambda expression.
/// </summary>
/// <param name="delegateOrLambdaExpression">The delegate or lambda expression.</param>
/// <returns>The code block.</returns>
private CSharpSyntaxNode GetBlockOrExpressionBodyOfDelegateOrLambdaExpression(SyntaxNode delegateOrLambdaExpression)
{
if (delegateOrLambdaExpression is AnonymousMethodExpressionSyntax anonymousMethod)
{
return anonymousMethod.Block;
}
if (delegateOrLambdaExpression is ParenthesizedLambdaExpressionSyntax lambdaExpression)
{
return lambdaExpression.Body;
}
throw new ArgumentException("Must be of type AnonymousMethodExpressionSyntax or ParenthesizedLambdaExpressionSyntax", nameof(delegateOrLambdaExpression));
}
/// <summary>
/// Walks up the syntax tree to find out where the specified delegate or lambda expression is being invoked.
/// </summary>
/// <param name="delegateOrLambdaExpression">Node representing a delegate or lambda expression.</param>
/// <returns>The invocation expression. Null if not found.</returns>
private InvocationExpressionSyntax FindInvocationOfDelegateOrLambdaExpression(SyntaxNode delegateOrLambdaExpression)
{
SyntaxNode currentNode = delegateOrLambdaExpression;
while (currentNode != null && !(currentNode is MethodDeclarationSyntax))
{
if (currentNode is InvocationExpressionSyntax invocationExpressionSyntax)
{
return invocationExpressionSyntax;
}
// Advance to the next parent
currentNode = currentNode.Parent;
}
return null;
}
/// <summary>
/// Checks whether the specified invocation is a call to JoinableTaskFactory.Run or RunAsync
/// </summary>
/// <param name="context">The analysis context.</param>
/// <param name="invocationExpressionSyntax">The invocation to check for.</param>
/// <returns>True if the specified invocation is a call to JoinableTaskFactory.Run or RunAsyn</returns>
private bool IsInvocationExpressionACallToJtfRun(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax invocationExpressionSyntax)
{
if (invocationExpressionSyntax.Expression is MemberAccessExpressionSyntax memberAccessExpressionSyntax)
{
// Check if we encountered a call to Run and had already encountered a delegate (so Run is a parent of the delegate)
string methodName = memberAccessExpressionSyntax.Name.Identifier.Text;
if (methodName == Types.JoinableTaskFactory.Run || methodName == Types.JoinableTaskFactory.RunAsync)
{
// Check whether the Run method belongs to JTF
IMethodSymbol methodSymbol = context.SemanticModel.GetSymbolInfo(memberAccessExpressionSyntax).Symbol as IMethodSymbol;
if (methodSymbol?.ContainingType != null &&
methodSymbol.ContainingType.Name == Types.JoinableTaskFactory.TypeName &&
methodSymbol.ContainingType.BelongsToNamespace(Types.JoinableTaskFactory.Namespace))
{
return true;
}
}
}
return false;
}
}
}

Просмотреть файл

@ -1,11 +1,8 @@
namespace Microsoft.VisualStudio.Threading.Analyzers
{
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CodeAnalysis;
using CodeAnalysis.CSharp;
using CodeAnalysis.CSharp.Syntax;
@ -29,6 +26,8 @@
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(this.AnalyzeInvocation), SyntaxKind.InvocationExpression);
context.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(this.AnalyzerObjectCreation), SyntaxKind.ObjectCreationExpression);
}

Просмотреть файл

@ -48,6 +48,8 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(this.AnalyzeInvocation), SyntaxKind.InvocationExpression);
}

Просмотреть файл

@ -0,0 +1,5 @@
[System.Windows.Threading.Dispatcher]::Invoke
[System.Windows.Threading.Dispatcher]::InvokeAsync
[System.Windows.Threading.Dispatcher]::BeginInvoke
[System.Threading.SynchronizationContext]::Send
[System.Threading.SynchronizationContext]::Post

Просмотреть файл

@ -69,7 +69,7 @@
[Fact]
public void SignalAndWaitSynchronousBlockDoesNotHang()
{
SynchronizationContext.SetSynchronizationContext(SingleThreadedSynchronizationContext.New());
SynchronizationContext.SetSynchronizationContext(SingleThreadedTestSynchronizationContext.New());
var evt = new AsyncCountdownEvent(1);
Assert.True(evt.SignalAndWaitAsync().Wait(AsyncDelay), "Hang");
}

Просмотреть файл

@ -0,0 +1,271 @@
/********************************************************
* *
* © Copyright (C) Microsoft. All rights reserved. *
* *
*********************************************************/
namespace Microsoft.VisualStudio.Threading.Tests
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
public class AsyncLazyInitializerTests : TestBase
{
public AsyncLazyInitializerTests(ITestOutputHelper logger)
: base(logger)
{
}
[Theory, CombinatorialData]
public void Ctor_NullAction(bool specifyJtf)
{
var jtf = specifyJtf ? new JoinableTaskContext().Factory : null; // use our own so we don't get main thread deadlocks, which isn't the point of this test.
Assert.Throws<ArgumentNullException>(() => new AsyncLazyInitializer(null, jtf));
}
[Fact]
public void Initialize_OnlyExecutesActionOnce_Success()
{
int invocationCount = 0;
var lazy = new AsyncLazyInitializer(async delegate
{
invocationCount++;
await Task.Yield();
});
Assert.Equal(0, invocationCount);
Assert.False(lazy.IsCompleted);
Assert.False(lazy.IsCompletedSuccessfully);
lazy.Initialize();
Assert.Equal(1, invocationCount);
Assert.True(lazy.IsCompleted);
Assert.True(lazy.IsCompletedSuccessfully);
lazy.Initialize();
Assert.Equal(1, invocationCount);
Assert.True(lazy.IsCompleted);
Assert.True(lazy.IsCompletedSuccessfully);
}
[Fact]
public async Task InitializeAsync_OnlyExecutesActionOnce_Success()
{
int invocationCount = 0;
var lazy = new AsyncLazyInitializer(async delegate
{
invocationCount++;
await Task.Yield();
});
Assert.Equal(0, invocationCount);
Assert.False(lazy.IsCompleted);
Assert.False(lazy.IsCompletedSuccessfully);
await lazy.InitializeAsync();
Assert.Equal(1, invocationCount);
Assert.True(lazy.IsCompleted);
Assert.True(lazy.IsCompletedSuccessfully);
await lazy.InitializeAsync();
Assert.Equal(1, invocationCount);
Assert.True(lazy.IsCompleted);
Assert.True(lazy.IsCompletedSuccessfully);
}
[Fact]
public void Initialize_OnlyExecutesActionOnce_Failure()
{
int invocationCount = 0;
var lazy = new AsyncLazyInitializer(async delegate
{
invocationCount++;
await Task.Yield();
throw new InvalidOperationException();
});
Assert.Equal(0, invocationCount);
Assert.False(lazy.IsCompleted);
Assert.False(lazy.IsCompletedSuccessfully);
Assert.Throws<InvalidOperationException>(() => lazy.Initialize());
Assert.Equal(1, invocationCount);
Assert.True(lazy.IsCompleted);
Assert.False(lazy.IsCompletedSuccessfully);
Assert.Throws<InvalidOperationException>(() => lazy.Initialize());
Assert.Equal(1, invocationCount);
Assert.True(lazy.IsCompleted);
Assert.False(lazy.IsCompletedSuccessfully);
}
[Fact]
public async Task InitializeAsync_OnlyExecutesActionOnce_Failure()
{
int invocationCount = 0;
var lazy = new AsyncLazyInitializer(async delegate
{
invocationCount++;
await Task.Yield();
throw new InvalidOperationException();
});
Assert.Equal(0, invocationCount);
Assert.False(lazy.IsCompleted);
Assert.False(lazy.IsCompletedSuccessfully);
await Assert.ThrowsAsync<InvalidOperationException>(() => lazy.InitializeAsync());
Assert.Equal(1, invocationCount);
Assert.True(lazy.IsCompleted);
Assert.False(lazy.IsCompletedSuccessfully);
await Assert.ThrowsAsync<InvalidOperationException>(() => lazy.InitializeAsync());
Assert.Equal(1, invocationCount);
Assert.True(lazy.IsCompleted);
Assert.False(lazy.IsCompletedSuccessfully);
}
/// <summary>
/// Verifies that even after the action has been invoked
/// its dependency on the Main thread can be satisfied by
/// someone synchronously blocking on the Main thread that is
/// also interested in its completion.
/// </summary>
[Fact]
public void InitializeAsync_RequiresMainThreadHeldByOther()
{
var context = this.InitializeJTCAndSC();
var jtf = context.Factory;
var evt = new AsyncManualResetEvent();
var lazy = new AsyncLazyInitializer(
async delegate
{
await evt; // use an event here to ensure it won't resume till the Main thread is blocked.
},
jtf);
var resultTask = lazy.InitializeAsync();
Assert.False(resultTask.IsCompleted);
var collection = context.CreateCollection();
var someRandomPump = context.CreateFactory(collection);
someRandomPump.Run(async delegate
{
evt.Set(); // setting this event allows the value factory to resume, once it can get the Main thread.
// The interesting bit we're testing here is that
// the value factory has already been invoked. It cannot
// complete until the Main thread is available and we're blocking
// the Main thread waiting for it to complete.
// This will deadlock unless the AsyncLazy joins
// the value factory's async pump with the currently blocking one.
await lazy.InitializeAsync();
});
// Now that the value factory has completed, the earlier acquired
// task should have no problem completing.
Assert.True(resultTask.Wait(AsyncDelay));
}
/// <summary>
/// Verifies that even after the action has been invoked
/// its dependency on the Main thread can be satisfied by
/// someone synchronously blocking on the Main thread that is
/// also interested in its completion.
/// </summary>
[Fact]
public void Initialize_RequiresMainThreadHeldByOther()
{
var context = this.InitializeJTCAndSC();
var jtf = context.Factory;
var evt = new AsyncManualResetEvent();
var lazy = new AsyncLazyInitializer(
async delegate
{
await evt; // use an event here to ensure it won't resume till the Main thread is blocked.
},
jtf);
var resultTask = lazy.InitializeAsync();
Assert.False(resultTask.IsCompleted);
evt.Set(); // setting this event allows the value factory to resume, once it can get the Main thread.
// The interesting bit we're testing here is that
// the value factory has already been invoked. It cannot
// complete until the Main thread is available and we're blocking
// the Main thread waiting for it to complete.
// This will deadlock unless the AsyncLazyExecution joins
// the action's JoinableTaskFactory with the currently blocking one.
lazy.Initialize();
// Now that the action has completed, the earlier acquired
// task should have no problem completing.
Assert.True(resultTask.Wait(AsyncDelay));
}
[Fact]
public async Task Initialize_Canceled()
{
var cts = new CancellationTokenSource();
var evt = new AsyncManualResetEvent();
var lazy = new AsyncLazyInitializer(evt.WaitAsync);
Task lazyTask = Task.Run(() => lazy.Initialize(cts.Token));
cts.Cancel();
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => lazyTask);
}
[Fact]
public async Task InitializeAsync_Canceled()
{
var cts = new CancellationTokenSource();
var evt = new AsyncManualResetEvent();
var lazy = new AsyncLazyInitializer(evt.WaitAsync);
Task lazyTask = lazy.InitializeAsync(cts.Token);
cts.Cancel();
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => lazyTask);
}
[Fact]
public void Initialize_Precanceled()
{
bool invoked = false;
var evt = new AsyncManualResetEvent();
var lazy = new AsyncLazyInitializer(delegate
{
invoked = true;
return evt.WaitAsync();
});
Assert.ThrowsAny<OperationCanceledException>(() => lazy.Initialize(new CancellationToken(canceled: true)));
Assert.False(invoked);
}
[Fact]
public async Task InitializeAsync_Precanceled()
{
bool invoked = false;
var evt = new AsyncManualResetEvent();
var lazy = new AsyncLazyInitializer(delegate
{
invoked = true;
return evt.WaitAsync();
});
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => lazy.InitializeAsync(new CancellationToken(canceled: true)));
Assert.False(invoked);
}
private JoinableTaskContext InitializeJTCAndSC()
{
SynchronizationContext.SetSynchronizationContext(SingleThreadedTestSynchronizationContext.New());
return new JoinableTaskContext();
}
}
}

Просмотреть файл

@ -197,7 +197,7 @@ namespace Microsoft.VisualStudio.Threading.Tests
var context = new JoinableTaskContext(); // we need our own collectible context.
collectible = new WeakReference(context.Factory);
var valueFactory = throwInValueFactory
? new Func<Task<object>>(delegate { throw new ApplicationException(); })
? new Func<Task<object>>(() => throw new ApplicationException())
: async delegate
{
await Task.Yield();
@ -334,6 +334,86 @@ namespace Microsoft.VisualStudio.Threading.Tests
Assert.True(lazy.IsValueFactoryCompleted);
}
[Theory, CombinatorialData]
public void GetValue(bool specifyJtf)
{
var jtf = specifyJtf ? new JoinableTaskContext().Factory : null; // use our own so we don't get main thread deadlocks, which isn't the point of this test.
var lazy = new AsyncLazy<GenericParameterHelper>(() => Task.FromResult(new GenericParameterHelper(5)), jtf);
Assert.Equal(5, lazy.GetValue().Data);
}
[Theory, CombinatorialData]
public void GetValue_Precanceled(bool specifyJtf)
{
var jtf = specifyJtf ? new JoinableTaskContext().Factory : null; // use our own so we don't get main thread deadlocks, which isn't the point of this test.
var lazy = new AsyncLazy<GenericParameterHelper>(() => Task.FromResult(new GenericParameterHelper(5)), jtf);
Assert.Throws<OperationCanceledException>(() => lazy.GetValue(new CancellationToken(canceled: true)));
}
[Theory, CombinatorialData]
public async Task GetValue_CalledAfterGetValueAsyncHasCompleted(bool specifyJtf)
{
var jtf = specifyJtf ? new JoinableTaskContext().Factory : null; // use our own so we don't get main thread deadlocks, which isn't the point of this test.
var lazy = new AsyncLazy<GenericParameterHelper>(() => Task.FromResult(new GenericParameterHelper(5)), jtf);
var result = await lazy.GetValueAsync();
Assert.Same(result, lazy.GetValue());
}
[Theory, CombinatorialData]
public async Task GetValue_CalledAfterGetValueAsync_InProgress(bool specifyJtf)
{
var completeValueFactory = new AsyncManualResetEvent();
var jtf = specifyJtf ? new JoinableTaskContext().Factory : null; // use our own so we don't get main thread deadlocks, which isn't the point of this test.
var lazy = new AsyncLazy<GenericParameterHelper>(
async delegate
{
await completeValueFactory;
return new GenericParameterHelper(5);
},
jtf);
Task<GenericParameterHelper> getValueAsyncTask = lazy.GetValueAsync();
Task<GenericParameterHelper> getValueTask = Task.Run(() => lazy.GetValue());
Assert.False(getValueAsyncTask.IsCompleted);
Assert.False(getValueTask.IsCompleted);
completeValueFactory.Set();
GenericParameterHelper[] results = await Task.WhenAll(getValueAsyncTask, getValueTask);
Assert.Same(results[0], results[1]);
}
[Theory, CombinatorialData]
public async Task GetValue_ThenCanceled(bool specifyJtf)
{
var completeValueFactory = new AsyncManualResetEvent();
var jtf = specifyJtf ? new JoinableTaskContext().Factory : null; // use our own so we don't get main thread deadlocks, which isn't the point of this test.
var lazy = new AsyncLazy<GenericParameterHelper>(
async delegate
{
await completeValueFactory;
return new GenericParameterHelper(5);
},
jtf);
var cts = new CancellationTokenSource();
Task<GenericParameterHelper> getValueTask = Task.Run(() => lazy.GetValue(cts.Token));
Assert.False(getValueTask.IsCompleted);
cts.Cancel();
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => getValueTask);
}
[Theory, CombinatorialData]
public void GetValue_ValueFactoryThrows(bool specifyJtf)
{
var exception = new InvalidOperationException();
var completeValueFactory = new AsyncManualResetEvent();
var jtf = specifyJtf ? new JoinableTaskContext().Factory : null; // use our own so we don't get main thread deadlocks, which isn't the point of this test.
var lazy = new AsyncLazy<GenericParameterHelper>(() => throw exception, jtf);
// Verify that we throw the right exception the first time.
Assert.Same(exception, Assert.Throws(exception.GetType(), () => lazy.GetValue()));
// Assert that we rethrow the exception the second time.
Assert.Same(exception, Assert.Throws(exception.GetType(), () => lazy.GetValue()));
}
[Fact]
public void ToStringForUncreatedValue()
{
@ -372,8 +452,8 @@ namespace Microsoft.VisualStudio.Threading.Tests
public void AsyncLazy_CompletesOnThreadWithValueFactory(NamedSyncContexts invokeOn, NamedSyncContexts completeOn)
{
// Set up various SynchronizationContexts that we may invoke or complete the async method with.
var aSyncContext = SingleThreadedSynchronizationContext.New();
var bSyncContext = SingleThreadedSynchronizationContext.New();
var aSyncContext = SingleThreadedTestSynchronizationContext.New();
var bSyncContext = SingleThreadedTestSynchronizationContext.New();
var invokeOnSyncContext = invokeOn == NamedSyncContexts.None ? null
: invokeOn == NamedSyncContexts.A ? aSyncContext
: invokeOn == NamedSyncContexts.B ? bSyncContext
@ -459,7 +539,7 @@ namespace Microsoft.VisualStudio.Threading.Tests
[CombinatorialData]
public void ValueFactoryRequiresMainThreadHeldByOtherSync(bool passJtfToLazyCtor)
{
var ctxt = SingleThreadedSynchronizationContext.New();
var ctxt = SingleThreadedTestSynchronizationContext.New();
SynchronizationContext.SetSynchronizationContext(ctxt);
var context = new JoinableTaskContext();
var asyncPump = context.Factory;
@ -489,10 +569,10 @@ namespace Microsoft.VisualStudio.Threading.Tests
Thread.Sleep(AsyncDelay); // Give the background thread time to call GetValueAsync(), but it doesn't yield (when the test was written).
var foregroundRequest = lazy.GetValueAsync();
var frame = SingleThreadedSynchronizationContext.NewFrame();
var frame = SingleThreadedTestSynchronizationContext.NewFrame();
var combinedTask = Task.WhenAll(foregroundRequest, backgroundRequest);
combinedTask.WithTimeout(UnexpectedTimeout).ContinueWith(_ => frame.Continue = false, TaskScheduler.Default);
SingleThreadedSynchronizationContext.PushFrame(ctxt, frame);
SingleThreadedTestSynchronizationContext.PushFrame(ctxt, frame);
// Ensure that the test didn't simply timeout, and that the individual tasks did not throw.
Assert.True(foregroundRequest.IsCompleted);
@ -507,7 +587,7 @@ namespace Microsoft.VisualStudio.Threading.Tests
[Fact]
public void ValueFactoryRequiresMainThreadHeldByOtherInJTFRun()
{
var ctxt = SingleThreadedSynchronizationContext.New();
var ctxt = SingleThreadedTestSynchronizationContext.New();
SynchronizationContext.SetSynchronizationContext(ctxt);
var context = new JoinableTaskContext();
var asyncPump = context.Factory;
@ -543,6 +623,46 @@ namespace Microsoft.VisualStudio.Threading.Tests
});
}
/// <summary>
/// Verifies that no deadlock occurs if the value factory synchronously blocks while switching to the UI thread
/// and the UI thread then uses <see cref="AsyncLazy{T}.GetValue()"/>.
/// </summary>
[Fact]
public void ValueFactoryRequiresMainThreadHeldByOtherInGetValue()
{
var ctxt = SingleThreadedTestSynchronizationContext.New();
SynchronizationContext.SetSynchronizationContext(ctxt);
var context = new JoinableTaskContext();
var asyncPump = context.Factory;
var originalThread = Thread.CurrentThread;
var evt = new AsyncManualResetEvent();
var lazy = new AsyncLazy<object>(
async delegate
{
// It is important that no await appear before this JTF.Run call, since
// we're testing that the value factory is not invoked while the AsyncLazy
// holds a private lock that would deadlock when called from another thread.
asyncPump.Run(async delegate
{
await asyncPump.SwitchToMainThreadAsync(this.TimeoutToken);
});
await Task.Yield();
return new object();
},
asyncPump);
var backgroundRequest = Task.Run(async delegate
{
return await lazy.GetValueAsync();
});
Thread.Sleep(AsyncDelay); // Give the background thread time to call GetValueAsync(), but it doesn't yield (when the test was written).
var foregroundValue = lazy.GetValue(this.TimeoutToken);
var backgroundValue = asyncPump.Run(() => backgroundRequest);
Assert.Same(foregroundValue, backgroundValue);
}
[Fact]
public async Task ExecutionContextFlowsFromFirstCaller_NoJTF()
{
@ -620,7 +740,7 @@ namespace Microsoft.VisualStudio.Threading.Tests
private JoinableTaskContext InitializeJTCAndSC()
{
SynchronizationContext.SetSynchronizationContext(SingleThreadedSynchronizationContext.New());
SynchronizationContext.SetSynchronizationContext(SingleThreadedTestSynchronizationContext.New());
return new JoinableTaskContext();
}
}

Просмотреть файл

@ -227,5 +227,15 @@
Assert.True(presignaledEvent.WaitAsync().IsCompleted);
}
}
[Fact]
public async Task WaitAsyncWithCancellationToken()
{
var cts = new CancellationTokenSource();
Task waitTask = this.evt.WaitAsync(cts.Token);
cts.Cancel();
var ex = await Assert.ThrowsAnyAsync<OperationCanceledException>(() => waitTask);
Assert.Equal(cts.Token, ex.CancellationToken);
}
}
}

Просмотреть файл

@ -496,9 +496,7 @@
{
await Task.Run(async delegate
{
#pragma warning disable SA1501 // Statement must not be on a single line: buggy analyzer misfires
Func<Task> yieldingDelegate = async () => { await Task.Yield(); };
#pragma warning restore SA1501 // Statement must not be on a single line
var asyncLock = new LockDerived
{
OnBeforeExclusiveLockReleasedAsyncDelegate = yieldingDelegate,
@ -565,7 +563,7 @@
[StaFact]
public async Task IsAnyLockHeldReturnsFalseForIncompatibleSyncContexts()
{
var dispatcher = SingleThreadedSynchronizationContext.New();
var dispatcher = SingleThreadedTestSynchronizationContext.New();
var asyncLock = new LockDerived();
using (await asyncLock.ReadLockAsync())
{
@ -578,7 +576,7 @@
[StaFact]
public async Task IsAnyPassiveLockHeldReturnsTrueForIncompatibleSyncContexts()
{
var dispatcher = SingleThreadedSynchronizationContext.New();
var dispatcher = SingleThreadedTestSynchronizationContext.New();
var asyncLock = new LockDerived();
using (await asyncLock.ReadLockAsync())
{
@ -591,7 +589,7 @@
[StaFact]
public async Task IsPassiveReadLockHeldReturnsTrueForIncompatibleSyncContexts()
{
var dispatcher = SingleThreadedSynchronizationContext.New();
var dispatcher = SingleThreadedTestSynchronizationContext.New();
using (await this.asyncLock.ReadLockAsync())
{
Assert.True(this.asyncLock.IsPassiveReadLockHeld);
@ -603,7 +601,7 @@
[StaFact]
public async Task IsPassiveUpgradeableReadLockHeldReturnsTrueForIncompatibleSyncContexts()
{
var dispatcher = SingleThreadedSynchronizationContext.New();
var dispatcher = SingleThreadedTestSynchronizationContext.New();
using (await this.asyncLock.UpgradeableReadLockAsync())
{
Assert.True(this.asyncLock.IsPassiveUpgradeableReadLockHeld);
@ -615,7 +613,7 @@
[StaFact]
public async Task IsPassiveWriteLockHeldReturnsTrueForIncompatibleSyncContexts()
{
var dispatcher = SingleThreadedSynchronizationContext.New();
var dispatcher = SingleThreadedTestSynchronizationContext.New();
using (var releaser = await this.asyncLock.WriteLockAsync())
{
Assert.True(this.asyncLock.IsPassiveWriteLockHeld);
@ -1813,6 +1811,67 @@
Assert.True(firstLockTask.Wait(TestTimeout)); // rethrow any exceptions
}
/// <summary>
/// Test to verify that we don't block the code to dispose a write lock, when it has been released, and a new write lock was issued right between Release and Dispose.
/// That happens in the original implementation, because it shares a same NonConcurrentSynchronizationContext, so a new write lock can take over it, and block the original lock task
/// to resume back to the context.
/// </summary>
[StaFact]
public void WriteLockDisposingShouldNotBlockByOtherWriters()
{
var firstLockToRelease = new AsyncManualResetEvent();
var firstLockAccquired = new AsyncManualResetEvent();
var firstLockToDispose = new AsyncManualResetEvent();
var firstLockTask = Task.Run(async delegate
{
using (var firstLock = await this.asyncLock.WriteLockAsync())
{
firstLockAccquired.Set();
await firstLockToRelease.WaitAsync();
await firstLock.ReleaseAsync();
// Wait for the second lock to be issued
await firstLockToDispose.WaitAsync();
}
});
var secondLockReleased = new TaskCompletionSource<int>();
var secondLockTask = Task.Run(async delegate
{
await firstLockAccquired.WaitAsync();
var awaiter = this.asyncLock.WriteLockAsync().GetAwaiter();
Assert.False(awaiter.IsCompleted);
awaiter.OnCompleted(() =>
{
try
{
using (var access = awaiter.GetResult())
{
firstLockToDispose.Set();
// We must block the thread synchronously, so it won't release the NonConcurrentSynchronizationContext until the first lock is completely disposed.
firstLockTask.Wait(TestTimeout * 2);
}
}
catch (Exception ex)
{
secondLockReleased.TrySetException(ex);
}
secondLockReleased.TrySetResult(0);
});
firstLockToRelease.Set();
// clean up logic
await firstLockTask;
await secondLockReleased.Task;
});
Assert.True(secondLockTask.Wait(TestTimeout)); // rethrow any exceptions
Assert.False(secondLockReleased.Task.IsFaulted);
}
[StaFact]
public async Task WriteLockAsyncSimple()
{

Просмотреть файл

@ -230,8 +230,8 @@ namespace Microsoft.VisualStudio.Threading.Tests
public void ConfigureAwaitRunInline_NoExtraThreadSwitching(NamedSyncContexts invokeOn, NamedSyncContexts completeOn)
{
// Set up various SynchronizationContexts that we may invoke or complete the async method with.
var aSyncContext = SingleThreadedSynchronizationContext.New();
var bSyncContext = SingleThreadedSynchronizationContext.New();
var aSyncContext = SingleThreadedTestSynchronizationContext.New();
var bSyncContext = SingleThreadedTestSynchronizationContext.New();
var invokeOnSyncContext = invokeOn == NamedSyncContexts.None ? null
: invokeOn == NamedSyncContexts.A ? aSyncContext
: invokeOn == NamedSyncContexts.B ? bSyncContext
@ -266,8 +266,8 @@ namespace Microsoft.VisualStudio.Threading.Tests
public void ConfigureAwaitRunInlineOfT_NoExtraThreadSwitching(NamedSyncContexts invokeOn, NamedSyncContexts completeOn)
{
// Set up various SynchronizationContexts that we may invoke or complete the async method with.
var aSyncContext = SingleThreadedSynchronizationContext.New();
var bSyncContext = SingleThreadedSynchronizationContext.New();
var aSyncContext = SingleThreadedTestSynchronizationContext.New();
var bSyncContext = SingleThreadedTestSynchronizationContext.New();
var invokeOnSyncContext = invokeOn == NamedSyncContexts.None ? null
: invokeOn == NamedSyncContexts.A ? aSyncContext
: invokeOn == NamedSyncContexts.B ? bSyncContext

Просмотреть файл

@ -0,0 +1,279 @@
namespace Microsoft.VisualStudio.Threading.Tests
{
using System;
using System.Threading;
using Xunit;
using Xunit.Abstractions;
public class CancellationTokenExtensionsTests : TestBase
{
public CancellationTokenExtensionsTests(ITestOutputHelper logger)
: base(logger)
{
}
[Fact]
public void CombineWith_NoneCancelable()
{
using (var combined = CancellationToken.None.CombineWith(CancellationToken.None))
{
Assert.False(combined.Token.CanBeCanceled);
}
}
[Fact]
public void CombineWith_FirstCancelable()
{
var cts = new CancellationTokenSource();
using (var combined = cts.Token.CombineWith(CancellationToken.None))
{
Assert.True(combined.Token.CanBeCanceled);
Assert.Equal(cts.Token, combined.Token);
}
}
[Fact]
public void CombineWith_SecondCancelable()
{
var cts = new CancellationTokenSource();
using (var combined = CancellationToken.None.CombineWith(cts.Token))
{
Assert.True(combined.Token.CanBeCanceled);
Assert.Equal(cts.Token, combined.Token);
}
}
[Fact]
public void CombineWith_BothCancelable()
{
var cts1 = new CancellationTokenSource();
var cts2 = new CancellationTokenSource();
using (var combined = cts1.Token.CombineWith(cts2.Token))
{
Assert.True(combined.Token.CanBeCanceled);
Assert.NotEqual(cts1.Token, combined.Token);
Assert.NotEqual(cts2.Token, combined.Token);
cts1.Cancel();
Assert.True(combined.Token.IsCancellationRequested);
}
}
[Fact]
public void CombineWith_BothCancelable_FirstAlreadyCanceled()
{
var first = new CancellationToken(true);
var cts2 = new CancellationTokenSource();
using (var combined = first.CombineWith(cts2.Token))
{
Assert.Equal(first, combined.Token);
}
}
[Fact]
public void CombineWith_BothCancelable_SecondAlreadyCanceled()
{
var cts1 = new CancellationTokenSource();
var second = new CancellationToken(true);
using (var combined = cts1.Token.CombineWith(second))
{
Assert.Equal(second, combined.Token);
}
}
[Fact]
public void CombineWith_Array_Empty()
{
var cts1 = new CancellationTokenSource();
using (var combined = cts1.Token.CombineWith())
{
Assert.Equal(cts1.Token, combined.Token);
}
}
[Fact]
public void CombineWith_Array_Null()
{
var cts1 = new CancellationTokenSource();
Assert.Throws<ArgumentNullException>(() => cts1.Token.CombineWith(null));
}
[Fact]
public void CombineWith_Array_Empty_OriginalNonCancelable()
{
using (var combined = CancellationToken.None.CombineWith())
{
Assert.False(combined.Token.CanBeCanceled);
}
}
[Fact]
public void CombineWith_Array_Empty_OriginalAlreadyCanceled()
{
CancellationToken cancellationToken = new CancellationToken(true);
using (var combined = cancellationToken.CombineWith())
{
Assert.True(combined.Token.IsCancellationRequested);
Assert.Equal(cancellationToken, combined.Token);
}
}
[Fact]
public void CombineWith_Array_OneArrayElementCancelable_First()
{
var cts1 = new CancellationTokenSource();
using (var combined = CancellationToken.None.CombineWith(cts1.Token, CancellationToken.None))
{
Assert.Equal(cts1.Token, combined.Token);
}
}
[Fact]
public void CombineWith_Array_OneArrayElementCancelable_Second()
{
var cts1 = new CancellationTokenSource();
using (var combined = CancellationToken.None.CombineWith(cts1.Token, CancellationToken.None))
{
Assert.Equal(cts1.Token, combined.Token);
}
}
[Fact]
public void CombineWith_Array_OneArrayElementPreCanceled()
{
var ct = new CancellationToken(true);
using (var combined = CancellationToken.None.CombineWith(CancellationToken.None, ct, CancellationToken.None))
{
Assert.Equal(ct, combined.Token);
Assert.True(combined.Token.IsCancellationRequested);
}
}
[Fact]
public void CombineWith_Array_TwoArrayElementsCancelable()
{
var cts1 = new CancellationTokenSource();
var cts2 = new CancellationTokenSource();
using (var combined = CancellationToken.None.CombineWith(cts1.Token, cts2.Token))
{
Assert.True(combined.Token.CanBeCanceled);
Assert.NotEqual(cts1.Token, combined.Token);
Assert.NotEqual(cts2.Token, combined.Token);
cts1.Cancel();
Assert.True(combined.Token.IsCancellationRequested);
}
}
[Fact]
public void CombineWith_Array_TwoCancelable()
{
var cts1 = new CancellationTokenSource();
var cts2 = new CancellationTokenSource();
using (var combined = cts1.Token.CombineWith(cts2.Token, CancellationToken.None))
{
Assert.NotEqual(cts1.Token, combined.Token);
Assert.NotEqual(cts2.Token, combined.Token);
cts2.Cancel();
Assert.True(combined.Token.IsCancellationRequested);
}
}
[Theory]
[CombinatorialData]
public void CombineWith_Array_TwoCancelable_AmidMany_FirstOriginal(bool cancelFirst)
{
var cts1 = new CancellationTokenSource();
var cts2 = new CancellationTokenSource();
using (var combined = cts1.Token.CombineWith(CancellationToken.None, cts2.Token, CancellationToken.None))
{
Assert.NotEqual(cts1.Token, combined.Token);
Assert.NotEqual(cts2.Token, combined.Token);
if (cancelFirst)
{
cts1.Cancel();
}
else
{
cts2.Cancel();
}
Assert.True(combined.Token.IsCancellationRequested);
}
}
[Theory]
[CombinatorialData]
public void CombineWith_Array_TwoCancelable_AmidMany(bool cancelFirst)
{
var cts1 = new CancellationTokenSource();
var cts2 = new CancellationTokenSource();
using (var combined = CancellationToken.None.CombineWith(cts1.Token, CancellationToken.None, cts2.Token, CancellationToken.None))
{
Assert.NotEqual(cts1.Token, combined.Token);
Assert.NotEqual(cts2.Token, combined.Token);
if (cancelFirst)
{
cts1.Cancel();
}
else
{
cts2.Cancel();
}
Assert.True(combined.Token.IsCancellationRequested);
}
}
[Fact]
public void CombineWith_Array_ThreeCancelable_AmidMany()
{
var cts1 = new CancellationTokenSource();
var cts2 = new CancellationTokenSource();
var cts3 = new CancellationTokenSource();
using (var combined = CancellationToken.None.CombineWith(cts1.Token, CancellationToken.None, cts2.Token, CancellationToken.None, cts3.Token))
{
Assert.NotEqual(cts1.Token, combined.Token);
Assert.NotEqual(cts2.Token, combined.Token);
Assert.NotEqual(cts3.Token, combined.Token);
cts2.Cancel();
Assert.True(combined.Token.IsCancellationRequested);
}
}
[Fact]
public void CombinedCancellationToken_Equality_BetweenEqualInstances_None()
{
var combined1 = CancellationToken.None.CombineWith(CancellationToken.None);
var combined2 = CancellationToken.None.CombineWith(CancellationToken.None);
Assert.Equal(combined1.GetHashCode(), combined2.GetHashCode());
Assert.True(combined1.Equals(combined2));
Assert.True(combined1 == combined2);
Assert.False(combined1 != combined2);
}
[Fact]
public void CombinedCancellationToken_Equality_WithRealToken()
{
var cts = new CancellationTokenSource();
var combined1 = cts.Token.CombineWith(CancellationToken.None);
var combined2 = cts.Token.CombineWith(CancellationToken.None);
Assert.Equal(combined1.GetHashCode(), combined2.GetHashCode());
Assert.True(combined1.Equals(combined2));
Assert.True(combined1 == combined2);
Assert.False(combined1 != combined2);
}
[Fact]
public void CombinedCancellationToken_Inequality_WithRealToken()
{
var cts = new CancellationTokenSource();
var combined1 = cts.Token.CombineWith(CancellationToken.None);
var combined2 = CancellationToken.None.CombineWith(CancellationToken.None);
Assert.NotEqual(combined1.GetHashCode(), combined2.GetHashCode());
Assert.False(combined1.Equals(combined2));
Assert.False(combined1 == combined2);
Assert.True(combined1 != combined2);
}
}
}

Просмотреть файл

@ -111,6 +111,25 @@
});
}
[StaFact]
public void JoinTillEmptyAsync_CancellationToken()
{
var tcs = new TaskCompletionSource<object>();
var joinable = this.JoinableFactory.RunAsync(async delegate
{
await tcs.Task;
});
this.context.Factory.Run(async delegate
{
var cts = new CancellationTokenSource();
Task joinTask = this.joinableCollection.JoinTillEmptyAsync(cts.Token);
cts.Cancel();
var ex = await Assert.ThrowsAnyAsync<OperationCanceledException>(() => joinTask);
Assert.Equal(cts.Token, ex.CancellationToken);
});
}
[StaFact]
public void AddTwiceRemoveOnceRemovesWhenNotRefCounting()
{

Просмотреть файл

@ -127,6 +127,22 @@
}
}
[StaFact]
public void SwitchToMainThreadAlwaysYield()
{
this.SimulateUIThread(async () =>
{
Assert.True(this.asyncPump.Context.IsOnMainThread);
Assert.False(this.asyncPump.SwitchToMainThreadAsync(alwaysYield: true).GetAwaiter().IsCompleted);
Assert.True(this.asyncPump.SwitchToMainThreadAsync(alwaysYield: false).GetAwaiter().IsCompleted);
await TaskScheduler.Default;
Assert.False(this.asyncPump.Context.IsOnMainThread);
Assert.False(this.asyncPump.SwitchToMainThreadAsync(alwaysYield: true).GetAwaiter().IsCompleted);
Assert.False(this.asyncPump.SwitchToMainThreadAsync(alwaysYield: false).GetAwaiter().IsCompleted);
});
}
/// <summary>
/// A <see cref="JoinableTaskFactory"/> that allows a test to inject code
/// in the main thread transition events.

Просмотреть файл

@ -22,18 +22,18 @@
protected int originalThreadManagedId;
protected SynchronizationContext dispatcherContext;
protected SingleThreadedSynchronizationContext.IFrame testFrame;
protected SingleThreadedTestSynchronizationContext.IFrame testFrame;
protected JoinableTaskTestBase(ITestOutputHelper logger)
: base(logger)
{
this.dispatcherContext = SingleThreadedSynchronizationContext.New();
this.dispatcherContext = SingleThreadedTestSynchronizationContext.New();
SynchronizationContext.SetSynchronizationContext(this.dispatcherContext);
this.context = this.CreateJoinableTaskContext();
this.joinableCollection = this.context.CreateCollection();
this.asyncPump = this.context.CreateFactory(this.joinableCollection);
this.originalThreadManagedId = Environment.CurrentManagedThreadId;
this.testFrame = SingleThreadedSynchronizationContext.NewFrame();
this.testFrame = SingleThreadedTestSynchronizationContext.NewFrame();
#if DESKTOP || NETCOREAPP2_0
// Suppress the assert dialog that appears and causes test runs to hang.
@ -75,7 +75,7 @@
}
}, null);
SingleThreadedSynchronizationContext.PushFrame(this.dispatcherContext, this.testFrame);
SingleThreadedTestSynchronizationContext.PushFrame(this.dispatcherContext, this.testFrame);
if (failure != null)
{
// Rethrow original exception without rewriting the callstack.
@ -85,7 +85,7 @@
protected void PushFrame()
{
SingleThreadedSynchronizationContext.PushFrame(this.dispatcherContext, this.testFrame);
SingleThreadedTestSynchronizationContext.PushFrame(this.dispatcherContext, this.testFrame);
}
protected void PushFrameTillQueueIsEmpty()

Просмотреть файл

@ -3084,7 +3084,7 @@
{
return completedTask;
});
}, maxBytesAllocated: 819);
}, maxBytesAllocated: 901);
}
}
@ -3606,6 +3606,102 @@
});
}
[Fact]
public void ExecutionContext_DoesNotLeakJoinableTask()
{
var longLivedTaskReleaser = new AsyncManualResetEvent();
WeakReference weakValue = this.ExecutionContext_DoesNotLeakJoinableTask_Helper(longLivedTaskReleaser);
try
{
// Assert that since no one wants the JoinableTask or its result any more, it has been released.
GC.Collect();
Assert.False(weakValue.IsAlive);
}
finally
{
// Allow completion of our long-lived task.
longLivedTaskReleaser.Set();
}
}
[Fact]
public void JoinableTask_TaskPropertyBeforeReturning()
{
this.SimulateUIThread(async delegate
{
var unblockJoinableTask = new ManualResetEventSlim();
var joinableTaskStarted = new AsyncManualResetEvent(allowInliningAwaiters: false);
JoinableTask observedJoinableTask = null;
Task observedWrappedTask = null;
var assertingTask = Task.Run(async delegate
{
try
{
await joinableTaskStarted.WaitAsync();
observedJoinableTask = this.joinableCollection.Single();
observedWrappedTask = observedJoinableTask.Task;
}
finally
{
unblockJoinableTask.Set();
}
});
var joinableTask = this.asyncPump.RunAsync(delegate
{
joinableTaskStarted.Set();
// Synchronously block *BEFORE* yielding.
unblockJoinableTask.Wait();
return TplExtensions.CompletedTask;
});
await assertingTask; // observe failures.
await joinableTask;
Assert.Same(observedJoinableTask, joinableTask);
await joinableTask.Task;
await observedWrappedTask;
});
}
[Fact]
public void JoinableTaskOfT_TaskPropertyBeforeReturning()
{
this.SimulateUIThread(async delegate
{
var unblockJoinableTask = new ManualResetEventSlim();
var joinableTaskStarted = new AsyncManualResetEvent(allowInliningAwaiters: false);
JoinableTask<int> observedJoinableTask = null;
Task<int> observedWrappedTask = null;
var assertingTask = Task.Run(async delegate
{
try
{
await joinableTaskStarted.WaitAsync();
observedJoinableTask = (JoinableTask<int>)this.joinableCollection.Single();
observedWrappedTask = observedJoinableTask.Task;
}
finally
{
unblockJoinableTask.Set();
}
});
var joinableTask = this.asyncPump.RunAsync(delegate
{
joinableTaskStarted.Set();
// Synchronously block *BEFORE* yielding.
unblockJoinableTask.Wait();
return Task.FromResult(3);
});
await assertingTask; // observe failures.
await joinableTask;
Assert.Same(observedJoinableTask, joinableTask);
Assert.Equal(3, await joinableTask.Task);
Assert.Equal(3, await observedWrappedTask);
});
}
protected override JoinableTaskContext CreateJoinableTaskContext()
{
return new DerivedJoinableTaskContext();
@ -3738,6 +3834,32 @@
this.Logger.WriteLine(report.Content);
}
[MethodImpl(MethodImplOptions.NoInlining)] // must not be inlined so that locals are guaranteed to be freed.
private WeakReference ExecutionContext_DoesNotLeakJoinableTask_Helper(AsyncManualResetEvent releaser)
{
object leakedValue = new object();
WeakReference weakValue = new WeakReference(leakedValue);
Task longLivedTask = null;
this.asyncPump.RunAsync(delegate
{
// Spin off a task that will "capture" the current running JoinableTask
longLivedTask = Task.Factory.StartNew(
async r =>
{
await ((AsyncManualResetEvent)r).WaitAsync();
},
releaser,
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.Default).Unwrap();
return Task.FromResult(leakedValue);
});
return weakValue;
}
/// <summary>
/// Simulates COM message pump reentrancy causing some unrelated work to "pump in" on top of a synchronously blocking wait.
/// </summary>
@ -3913,7 +4035,7 @@
{
Assert.NotNull(this.UnderlyingSynchronizationContext);
Assert.NotNull(callback);
Assert.True(SingleThreadedSynchronizationContext.IsSingleThreadedSyncContext(this.UnderlyingSynchronizationContext));
Assert.True(SingleThreadedTestSynchronizationContext.IsSingleThreadedSyncContext(this.UnderlyingSynchronizationContext));
base.PostToUnderlyingSynchronizationContext(callback, state);
this.PostToUnderlyingSynchronizationContextCallback?.Invoke();
}

Просмотреть файл

@ -69,7 +69,7 @@
[Fact]
public void SynchronizationContextCaptured()
{
var syncContext = SingleThreadedSynchronizationContext.New();
var syncContext = SingleThreadedTestSynchronizationContext.New();
SynchronizationContext.SetSynchronizationContext(syncContext);
Exception callbackError = null;
var callback = new Action<GenericParameterHelper>(

Просмотреть файл

@ -21,7 +21,7 @@ public abstract class ReentrantSemaphoreTestBase : TestBase, IDisposable
public ReentrantSemaphoreTestBase(ITestOutputHelper logger)
: base(logger)
{
this.Dispatcher = SingleThreadedSynchronizationContext.New();
this.Dispatcher = SingleThreadedTestSynchronizationContext.New();
}
public static object[][] SemaphoreCapacitySizes
@ -119,6 +119,54 @@ public abstract class ReentrantSemaphoreTestBase : TestBase, IDisposable
});
}
[Theory]
[MemberData(nameof(AllModes))]
public void ExecuteAsync_OfT_InvokesDelegateInOriginalContext_NoContention(ReentrantSemaphore.ReentrancyMode mode)
{
this.semaphore = this.CreateSemaphore(mode);
int originalThreadId = Environment.CurrentManagedThreadId;
this.ExecuteOnDispatcher(async delegate
{
bool executed = false;
int result = await this.semaphore.ExecuteAsync(
delegate
{
Assert.Equal(originalThreadId, Environment.CurrentManagedThreadId);
executed = true;
return new ValueTask<int>(5);
}, this.TimeoutToken);
Assert.Equal(5, result);
Assert.True(executed);
});
}
[Theory]
[MemberData(nameof(AllModes))]
public void ExecuteAsync_OfT_InvokesDelegateInOriginalContext_WithContention(ReentrantSemaphore.ReentrancyMode mode)
{
this.semaphore = this.CreateSemaphore(mode);
int originalThreadId = Environment.CurrentManagedThreadId;
this.ExecuteOnDispatcher(async delegate
{
var releaseHolder = new AsyncManualResetEvent();
var holder = this.semaphore.ExecuteAsync(() => releaseHolder.WaitAsync());
bool executed = false;
var waiter = this.semaphore.ExecuteAsync(
delegate
{
Assert.Equal(originalThreadId, Environment.CurrentManagedThreadId);
executed = true;
return new ValueTask<int>(5);
}, this.TimeoutToken);
releaseHolder.Set();
int result = await waiter.AsTask().WithCancellation(this.TimeoutToken);
Assert.Equal(5, result);
Assert.True(executed);
});
}
[Theory]
[MemberData(nameof(AllModes))]
public void ExecuteAsync_NullDelegate(ReentrantSemaphore.ReentrancyMode mode)
@ -130,6 +178,17 @@ public abstract class ReentrantSemaphoreTestBase : TestBase, IDisposable
});
}
[Theory]
[MemberData(nameof(AllModes))]
public void ExecuteAsync_OfT_NullDelegate(ReentrantSemaphore.ReentrancyMode mode)
{
this.semaphore = this.CreateSemaphore(mode);
this.ExecuteOnDispatcher(async delegate
{
await Assert.ThrowsAsync<ArgumentNullException>(() => this.semaphore.ExecuteAsync<int>(null, this.TimeoutToken).AsTask());
});
}
[Theory]
[MemberData(nameof(AllModes))]
public void ExecuteAsync_Contested(ReentrantSemaphore.ReentrancyMode mode)

Просмотреть файл

@ -1,241 +0,0 @@
#if DESKTOP
#define UseWpfContext
#endif
namespace Microsoft.VisualStudio.Threading.Tests
{
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
#if DESKTOP
using System.Windows.Threading;
#endif
/// <summary>
/// A single-threaded synchronization context, akin to the DispatcherSynchronizationContext
/// and WindowsFormsSynchronizationContext.
/// </summary>
/// <remarks>
/// We don't use either DispatcherSynchronizationContext or WindowsFormsSynchronizationContext
/// in tests because the first is not implemented on mono and the latter is poorly implemented in mono.
/// </remarks>
public static class SingleThreadedSynchronizationContext
{
public interface IFrame
{
bool Continue { get; set; }
}
public static SynchronizationContext New()
{
#if UseWpfContext
return new DispatcherSynchronizationContext();
#else
return new SyncContext();
#endif
}
public static bool IsSingleThreadedSyncContext(SynchronizationContext context)
{
#if UseWpfContext
return context is DispatcherSynchronizationContext;
#else
return context is SyncContext;
#endif
}
public static IFrame NewFrame()
{
#if UseWpfContext
return new WpfWrapperFrame();
#else
return new SyncContext.Frame();
#endif
}
public static void PushFrame(SynchronizationContext context, IFrame frame)
{
Requires.NotNull(context, nameof(context));
Requires.NotNull(frame, nameof(frame));
#if UseWpfContext
Dispatcher.PushFrame(((WpfWrapperFrame)frame).Frame);
#else
var ctxt = (SyncContext)context;
ctxt.PushFrame((SyncContext.Frame)frame);
#endif
}
#if UseWpfContext
private class WpfWrapperFrame : IFrame
{
internal readonly DispatcherFrame Frame = new DispatcherFrame();
public bool Continue
{
get { return this.Frame.Continue; }
set { this.Frame.Continue = value; }
}
}
#endif
private class SyncContext : SynchronizationContext
{
private readonly Queue<Message> messageQueue = new Queue<Message>();
private readonly int ownedThreadId = Environment.CurrentManagedThreadId;
public override void Post(SendOrPostCallback d, object state)
{
var ctxt = ExecutionContext.Capture();
lock (this.messageQueue)
{
this.messageQueue.Enqueue(new Message(d, state, ctxt));
Monitor.PulseAll(this.messageQueue);
}
}
public override void Send(SendOrPostCallback d, object state)
{
Requires.NotNull(d, nameof(d));
if (this.ownedThreadId == Environment.CurrentManagedThreadId)
{
d(state);
}
else
{
Exception caughtException = null;
var evt = new ManualResetEventSlim();
var ctxt = ExecutionContext.Capture();
lock (this.messageQueue)
{
this.messageQueue.Enqueue(new Message(
s =>
{
try
{
d(state);
}
catch (Exception ex)
{
caughtException = ex;
}
finally
{
evt.Set();
}
},
null,
ctxt));
Monitor.PulseAll(this.messageQueue);
}
evt.Wait();
if (caughtException != null)
{
throw new TargetInvocationException(caughtException);
}
}
}
public void PushFrame(Frame frame)
{
Requires.NotNull(frame, nameof(frame));
Verify.Operation(this.ownedThreadId == Environment.CurrentManagedThreadId, "Can only push a message pump from the owned thread.");
frame.SetOwner(this);
while (frame.Continue)
{
Message message;
lock (this.messageQueue)
{
// Check again now that we're holding the lock.
if (!frame.Continue)
{
break;
}
if (this.messageQueue.Count > 0)
{
message = this.messageQueue.Dequeue();
}
else
{
Monitor.Wait(this.messageQueue);
continue;
}
}
if (message.Context != null)
{
ExecutionContext.Run(
message.Context,
new ContextCallback(message.Callback),
message.State);
}
else
{
message.Callback(message.State);
}
}
}
private struct Message
{
public readonly SendOrPostCallback Callback;
public readonly object State;
public readonly ExecutionContext Context;
public Message(SendOrPostCallback d, object state, ExecutionContext ctxt)
: this()
{
this.Callback = d;
this.State = state;
this.Context = ctxt;
}
}
public class Frame : IFrame
{
private SyncContext owner;
private bool @continue = true;
public bool Continue
{
get
{
return this.@continue;
}
set
{
Verify.Operation(this.owner != null, "Frame not pushed yet.");
this.@continue = value;
// Alert thread that may be blocked waiting for an incoming message
// that it no longer needs to wait.
if (!value)
{
lock (this.owner.messageQueue)
{
Monitor.PulseAll(this.owner.messageQueue);
}
}
}
}
internal void SetOwner(SyncContext context)
{
if (context != this.owner)
{
Verify.Operation(this.owner == null, "Frame already associated with a SyncContext");
this.owner = context;
}
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,233 @@
/********************************************************
* *
* © Copyright (C) Microsoft. All rights reserved. *
* *
*********************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Threading;
using Microsoft.VisualStudio.Threading.Tests;
using Xunit;
using Xunit.Abstractions;
public class SingleThreadedSynchronizationContextTests : TestBase
{
public SingleThreadedSynchronizationContextTests(ITestOutputHelper logger)
: base(logger)
{
}
[Fact]
public void CreateCopy_ReturnsInstanceOfCorrectType()
{
var syncContext = new SingleThreadedSynchronizationContext();
Assert.IsType<SingleThreadedSynchronizationContext>(syncContext.CreateCopy());
}
[Fact]
public void CreateCopy_ReturnsNewInstance()
{
var syncContext = new SingleThreadedSynchronizationContext();
var other = syncContext.CreateCopy();
Assert.NotSame(syncContext, other);
// Verify that posting to the copy effectively gets the work to run on the original thread.
var frame = new SingleThreadedSynchronizationContext.Frame();
int? observedThreadId = null;
Task.Run(() =>
{
other.Post(
s =>
{
observedThreadId = Environment.CurrentManagedThreadId;
frame.Continue = false;
},
null);
});
syncContext.PushFrame(frame);
Assert.Equal(Environment.CurrentManagedThreadId, observedThreadId.Value);
}
[Fact]
public void Send_ExecutesDelegate_NoThrow()
{
var syncContext = new SingleThreadedSynchronizationContext();
int? executingThread = null;
syncContext.Send(s => executingThread = Environment.CurrentManagedThreadId, null);
Assert.Equal(Environment.CurrentManagedThreadId, executingThread);
}
[Fact]
public void Send_ExecutesDelegate_Throws()
{
var syncContext = new SingleThreadedSynchronizationContext();
Exception expected = new InvalidOperationException();
var actual = Assert.Throws<TargetInvocationException>(() => syncContext.Send(s => throw expected, null));
Assert.Same(expected, actual.InnerException);
}
[Fact]
public void Send_OnDifferentThread_ExecutesDelegateAndWaits()
{
int originalThreadId = Environment.CurrentManagedThreadId;
var syncContext = new SingleThreadedSynchronizationContext();
var frame = new SingleThreadedSynchronizationContext.Frame();
var task = Task.Run(delegate
{
try
{
int? observedThreadId = null;
syncContext.Send(s => observedThreadId = Environment.CurrentManagedThreadId, null);
Assert.Equal(originalThreadId, observedThreadId);
}
finally
{
frame.Continue = false;
}
});
syncContext.PushFrame(frame);
task.GetAwaiter().GetResult();
}
[Fact]
public void Send_OnDifferentThread_ExecutesDelegateAndWaits_Throws()
{
int originalThreadId = Environment.CurrentManagedThreadId;
var syncContext = new SingleThreadedSynchronizationContext();
var frame = new SingleThreadedSynchronizationContext.Frame();
var task = Task.Run(delegate
{
try
{
var expectedException = new InvalidOperationException();
var actualException = Assert.Throws<TargetInvocationException>(() => syncContext.Send(s => throw expectedException, null));
Assert.Same(expectedException, actualException.InnerException);
}
finally
{
frame.Continue = false;
}
});
syncContext.PushFrame(frame);
task.GetAwaiter().GetResult();
}
[Fact]
public void Post_DoesNotExecuteSynchronously()
{
var syncContext = new SingleThreadedSynchronizationContext();
int? executingThread = null;
syncContext.Post(s => executingThread = Environment.CurrentManagedThreadId, null);
Assert.Null(executingThread);
}
[Fact]
public void Post_PushFrame()
{
var originalThreadId = Environment.CurrentManagedThreadId;
var syncContext = new SingleThreadedSynchronizationContext();
var frame = new SingleThreadedSynchronizationContext.Frame();
var postedMessageCompletionSource = new TaskCompletionSource<object>();
syncContext.Post(
async state =>
{
try
{
Assert.Equal(originalThreadId, Environment.CurrentManagedThreadId);
await Task.Yield();
Assert.Equal(originalThreadId, Environment.CurrentManagedThreadId);
postedMessageCompletionSource.SetResult(null);
}
catch (Exception ex)
{
postedMessageCompletionSource.SetException(ex);
}
finally
{
frame.Continue = false;
}
},
null);
syncContext.PushFrame(frame);
Assert.True(postedMessageCompletionSource.Task.IsCompleted);
// Rethrow any exception.
postedMessageCompletionSource.Task.GetAwaiter().GetResult();
}
[Fact]
public void Post_PushFrame_Throws()
{
var originalThreadId = Environment.CurrentManagedThreadId;
var syncContext = new SingleThreadedSynchronizationContext();
var frame = new SingleThreadedSynchronizationContext.Frame();
var expectedException = new InvalidOperationException();
syncContext.Post(state => throw expectedException, null);
var actualException = Assert.Throws<InvalidOperationException>(() => syncContext.PushFrame(frame));
Assert.Same(expectedException, actualException);
}
[Fact]
public void Post_CapturesExecutionContext()
{
var syncContext = new SingleThreadedSynchronizationContext();
var frame = new SingleThreadedSynchronizationContext.Frame();
var task = Task.Run(async delegate
{
try
{
var expectedValue = new object();
var actualValue = new TaskCompletionSource<object>();
var asyncLocal = new AsyncLocal<object>();
asyncLocal.Value = expectedValue;
syncContext.Post(s => actualValue.SetResult(asyncLocal.Value), null);
Assert.Same(expectedValue, await actualValue.Task);
}
finally
{
frame.Continue = false;
}
});
syncContext.PushFrame(frame);
task.GetAwaiter().GetResult();
}
[Fact]
public void OperationStarted_OperationCompleted()
{
var syncContext = new SingleThreadedSynchronizationContext();
// Just make sure they don't throw when called properly.
syncContext.OperationStarted();
syncContext.OperationCompleted();
}
[Fact]
public void OperationStarted_OperationCompleted_Nested()
{
var syncContext = new SingleThreadedSynchronizationContext();
// Just make sure they don't throw when called properly.
syncContext.OperationStarted();
syncContext.OperationStarted();
syncContext.OperationCompleted();
syncContext.OperationCompleted();
}
}

Просмотреть файл

@ -0,0 +1,94 @@
#if DESKTOP
#define UseWpfContext
#endif
namespace Microsoft.VisualStudio.Threading.Tests
{
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
#if DESKTOP
using System.Windows.Threading;
#endif
/// <summary>
/// A single-threaded synchronization context, akin to the DispatcherSynchronizationContext
/// and WindowsFormsSynchronizationContext.
/// </summary>
/// <remarks>
/// We don't use either DispatcherSynchronizationContext or WindowsFormsSynchronizationContext
/// in tests because the first is not implemented on mono and the latter is poorly implemented in mono.
/// </remarks>
public static class SingleThreadedTestSynchronizationContext
{
public interface IFrame
{
bool Continue { get; set; }
}
public static SynchronizationContext New()
{
#if UseWpfContext
return new DispatcherSynchronizationContext();
#else
return new SingleThreadedSynchronizationContext();
#endif
}
public static bool IsSingleThreadedSyncContext(SynchronizationContext context)
{
#if UseWpfContext
return context is DispatcherSynchronizationContext;
#else
return context is SingleThreadedSynchronizationContext;
#endif
}
public static IFrame NewFrame()
{
#if UseWpfContext
return new WpfWrapperFrame();
#else
return new OwnWrapperFrame();
#endif
}
public static void PushFrame(SynchronizationContext context, IFrame frame)
{
Requires.NotNull(context, nameof(context));
Requires.NotNull(frame, nameof(frame));
#if UseWpfContext
Dispatcher.PushFrame(((WpfWrapperFrame)frame).Frame);
#else
var ctxt = (SingleThreadedSynchronizationContext)context;
ctxt.PushFrame(((OwnWrapperFrame)frame).Frame);
#endif
}
#if UseWpfContext
private class WpfWrapperFrame : IFrame
{
internal readonly DispatcherFrame Frame = new DispatcherFrame();
public bool Continue
{
get { return this.Frame.Continue; }
set { this.Frame.Continue = value; }
}
}
#else
private class OwnWrapperFrame : IFrame
{
internal readonly SingleThreadedSynchronizationContext.Frame Frame = new SingleThreadedSynchronizationContext.Frame();
public bool Continue
{
get { return this.Frame.Continue; }
set { this.Frame.Continue = value; }
}
}
#endif
}
}

Просмотреть файл

@ -198,11 +198,11 @@
{
// If there is a dispatcher sync context, let it run for a bit.
// This allows any posted messages that are now obsolete to be released.
if (SingleThreadedSynchronizationContext.IsSingleThreadedSyncContext(SynchronizationContext.Current))
if (SingleThreadedTestSynchronizationContext.IsSingleThreadedSyncContext(SynchronizationContext.Current))
{
var frame = SingleThreadedSynchronizationContext.NewFrame();
var frame = SingleThreadedTestSynchronizationContext.NewFrame();
SynchronizationContext.Current.Post(state => frame.Continue = false, null);
SingleThreadedSynchronizationContext.PushFrame(SynchronizationContext.Current, frame);
SingleThreadedTestSynchronizationContext.PushFrame(SynchronizationContext.Current, frame);
}
}
else
@ -300,7 +300,7 @@
{
Action worker = delegate
{
var frame = SingleThreadedSynchronizationContext.NewFrame();
var frame = SingleThreadedTestSynchronizationContext.NewFrame();
Exception failure = null;
SynchronizationContext.Current.Post(
async _ =>
@ -320,7 +320,7 @@
},
null);
SingleThreadedSynchronizationContext.PushFrame(SynchronizationContext.Current, frame);
SingleThreadedTestSynchronizationContext.PushFrame(SynchronizationContext.Current, frame);
if (failure != null)
{
ExceptionDispatchInfo.Capture(failure).Throw();
@ -329,7 +329,7 @@
#if DESKTOP || NETCOREAPP2_0
if ((!staRequired || Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) &&
SingleThreadedSynchronizationContext.IsSingleThreadedSyncContext(SynchronizationContext.Current))
SingleThreadedTestSynchronizationContext.IsSingleThreadedSyncContext(SynchronizationContext.Current))
{
worker();
}
@ -337,16 +337,16 @@
{
this.ExecuteOnSTA(() =>
{
if (!SingleThreadedSynchronizationContext.IsSingleThreadedSyncContext(SynchronizationContext.Current))
if (!SingleThreadedTestSynchronizationContext.IsSingleThreadedSyncContext(SynchronizationContext.Current))
{
SynchronizationContext.SetSynchronizationContext(SingleThreadedSynchronizationContext.New());
SynchronizationContext.SetSynchronizationContext(SingleThreadedTestSynchronizationContext.New());
}
worker();
});
}
#else
if (SingleThreadedSynchronizationContext.IsSingleThreadedSyncContext(SynchronizationContext.Current))
if (SingleThreadedTestSynchronizationContext.IsSingleThreadedSyncContext(SynchronizationContext.Current))
{
worker();
}
@ -354,7 +354,7 @@
{
Task.Run(delegate
{
SynchronizationContext.SetSynchronizationContext(SingleThreadedSynchronizationContext.New());
SynchronizationContext.SetSynchronizationContext(SingleThreadedTestSynchronizationContext.New());
worker();
}).GetAwaiter().GetResult();
}

Просмотреть файл

@ -45,7 +45,7 @@
var prevCtx = SynchronizationContext.Current;
try
{
var syncCtx = SingleThreadedSynchronizationContext.New();
var syncCtx = SingleThreadedTestSynchronizationContext.New();
SynchronizationContext.SetSynchronizationContext(syncCtx);
var t = func();
@ -54,9 +54,9 @@
throw new InvalidOperationException();
}
var frame = SingleThreadedSynchronizationContext.NewFrame();
var frame = SingleThreadedTestSynchronizationContext.NewFrame();
t.ContinueWith(_ => { frame.Continue = false; }, TaskScheduler.Default);
SingleThreadedSynchronizationContext.PushFrame(syncCtx, frame);
SingleThreadedTestSynchronizationContext.PushFrame(syncCtx, frame);
t.GetAwaiter().GetResult();
}

Просмотреть файл

@ -169,7 +169,7 @@
[Fact]
public void WithCancellationOfTNoDeadlockFromSyncContext()
{
var dispatcher = SingleThreadedSynchronizationContext.New();
var dispatcher = SingleThreadedTestSynchronizationContext.New();
SynchronizationContext.SetSynchronizationContext(dispatcher);
var tcs = new TaskCompletionSource<object>();
var cts = new CancellationTokenSource(AsyncDelay / 4);
@ -187,7 +187,7 @@
[Fact]
public void WithCancellationOfTNoncancelableNoDeadlockFromSyncContext()
{
var dispatcher = SingleThreadedSynchronizationContext.New();
var dispatcher = SingleThreadedTestSynchronizationContext.New();
SynchronizationContext.SetSynchronizationContext(dispatcher);
var tcs = new TaskCompletionSource<object>();
Task.Run(async delegate
@ -223,7 +223,7 @@
[Fact]
public void WithCancellationNoDeadlockFromSyncContext_Canceled()
{
var dispatcher = SingleThreadedSynchronizationContext.New();
var dispatcher = SingleThreadedTestSynchronizationContext.New();
SynchronizationContext.SetSynchronizationContext(dispatcher);
WithCancellationSyncBlock(simulateCancellation: true);
}
@ -231,7 +231,7 @@
[Fact]
public void WithCancellationNoDeadlockFromSyncContext_Completed()
{
var dispatcher = SingleThreadedSynchronizationContext.New();
var dispatcher = SingleThreadedTestSynchronizationContext.New();
SynchronizationContext.SetSynchronizationContext(dispatcher);
WithCancellationSyncBlock(simulateCancellation: false);
}
@ -239,7 +239,7 @@
[Fact]
public void WithCancellationNoncancelableNoDeadlockFromSyncContext()
{
var dispatcher = SingleThreadedSynchronizationContext.New();
var dispatcher = SingleThreadedTestSynchronizationContext.New();
SynchronizationContext.SetSynchronizationContext(dispatcher);
WithCancellationSyncBlockOnNoncancelableToken();
}
@ -247,7 +247,7 @@
[StaFact]
public void WithCancellationNoDeadlockFromSyncContextWithinJTFRun_Canceled()
{
var dispatcher = SingleThreadedSynchronizationContext.New();
var dispatcher = SingleThreadedTestSynchronizationContext.New();
SynchronizationContext.SetSynchronizationContext(dispatcher);
var jtc = new JoinableTaskContext();
jtc.Factory.Run(delegate
@ -260,7 +260,7 @@
[StaFact]
public void WithCancellationNoDeadlockFromSyncContextWithinJTFRun_Completed()
{
var dispatcher = SingleThreadedSynchronizationContext.New();
var dispatcher = SingleThreadedTestSynchronizationContext.New();
SynchronizationContext.SetSynchronizationContext(dispatcher);
var jtc = new JoinableTaskContext();
jtc.Factory.Run(delegate
@ -273,7 +273,7 @@
[StaFact]
public void WithCancellationNoncancelableNoDeadlockFromSyncContextWithinJTFRun()
{
var dispatcher = SingleThreadedSynchronizationContext.New();
var dispatcher = SingleThreadedTestSynchronizationContext.New();
SynchronizationContext.SetSynchronizationContext(dispatcher);
var jtc = new JoinableTaskContext();
jtc.Factory.Run(delegate

Просмотреть файл

@ -96,10 +96,7 @@ namespace Microsoft.VisualStudio.Threading
/// Thrown when the value factory calls <see cref="GetValueAsync()"/> on this instance.
/// </exception>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
public Task<T> GetValueAsync()
{
return this.GetValueAsync(CancellationToken.None);
}
public Task<T> GetValueAsync() => this.GetValueAsync(CancellationToken.None);
/// <summary>
/// Gets the task that produces or has produced the value.
@ -196,6 +193,47 @@ namespace Microsoft.VisualStudio.Threading
return this.value.WithCancellation(cancellationToken);
}
/// <summary>
/// Gets the lazily computed value.
/// </summary>
/// <returns>The lazily constructed value.</returns>
/// <exception cref="InvalidOperationException">
/// Thrown when the value factory calls <see cref="GetValueAsync()"/> on this instance.
/// </exception>
public T GetValue() => this.GetValue(CancellationToken.None);
/// <summary>
/// Gets the lazily computed value.
/// </summary>
/// <param name="cancellationToken">
/// A token whose cancellation indicates that the caller no longer is interested in the result.
/// Note that this will not cancel the value factory (since other callers may exist).
/// But when this token is canceled, the caller will experience an <see cref="OperationCanceledException"/>
/// immediately and a dis-joining of any <see cref="JoinableTask"/> that may have occurred as a result of this call.
/// </param>
/// <returns>The lazily constructed value.</returns>
/// <exception cref="InvalidOperationException">
/// Thrown when the value factory calls <see cref="GetValueAsync()"/> on this instance.
/// </exception>
/// <exception cref="OperationCanceledException">Thrown when <paramref name="cancellationToken"/> is canceled before the value is computed.</exception>
public T GetValue(CancellationToken cancellationToken)
{
// As a perf optimization, avoid calling JTF or GetValueAsync if the value factory has already completed.
if (this.IsValueFactoryCompleted)
{
return this.value.GetAwaiter().GetResult();
}
else
{
// Capture the factory as a local before comparing and dereferencing it since
// the field can transition to null and we want to gracefully handle that race condition.
JoinableTaskFactory factory = this.jobFactory;
return factory != null
? factory.Run(() => this.GetValueAsync(cancellationToken))
: this.GetValueAsync(cancellationToken).GetAwaiter().GetResult();
}
}
/// <summary>
/// Renders a string describing an uncreated value, or the string representation of the created value.
/// </summary>

Просмотреть файл

@ -0,0 +1,66 @@
/********************************************************
* *
* © Copyright (C) Microsoft. All rights reserved. *
* *
*********************************************************/
namespace Microsoft.VisualStudio.Threading
{
using System;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Lazily executes a delegate that has some side effect (typically initializing something)
/// such that the delegate runs at most once.
/// </summary>
public class AsyncLazyInitializer
{
/// <summary>
/// The lazy instance we use internally for the bulk of the behavior we want.
/// </summary>
private readonly AsyncLazy<EmptyStruct> lazy;
/// <summary>
/// Initializes a new instance of the <see cref="AsyncLazyInitializer"/> class.
/// </summary>
/// <param name="action">The action to perform at most once, that has some desirable side-effect.</param>
/// <param name="joinableTaskFactory">The factory to use when invoking the <paramref name="action"/> in <see cref="InitializeAsync(CancellationToken)"/> to avoid deadlocks when the main thread is required by the <paramref name="action"/>.</param>
public AsyncLazyInitializer(Func<Task> action, JoinableTaskFactory joinableTaskFactory = null)
{
Requires.NotNull(action, nameof(action));
this.lazy = new AsyncLazy<EmptyStruct>(
async delegate
{
await action().ConfigureAwaitRunInline();
return default;
},
joinableTaskFactory);
}
/// <summary>
/// Gets a value indicating whether the action has executed completely, regardless of whether it threw an exception.
/// </summary>
public bool IsCompleted => this.lazy.IsValueFactoryCompleted;
/// <summary>
/// Gets a value indicating whether the action has executed completely without throwing an exception.
/// </summary>
public bool IsCompletedSuccessfully => this.lazy.IsValueFactoryCompleted && this.lazy.GetValueAsync().Status == TaskStatus.RanToCompletion;
/// <summary>
/// Executes the action given in the constructor if it has not yet been executed,
/// or waits for it to complete if in progress from a prior call.
/// </summary>
/// <exception cref="Exception">Any exception thrown by the action is rethrown here.</exception>
public void Initialize(CancellationToken cancellationToken = default) => this.lazy.GetValue(cancellationToken);
/// <summary>
/// Executes the action given in the constructor if it has not yet been executed,
/// or waits for it to complete if in progress from a prior call.
/// </summary>
/// <returns>A task that tracks completion of the action.</returns>
/// <exception cref="Exception">Any exception thrown by the action is rethrown here.</exception>
public Task InitializeAsync(CancellationToken cancellationToken = default) => this.lazy.GetValueAsync(cancellationToken);
}
}

Просмотреть файл

@ -33,7 +33,7 @@ namespace Microsoft.VisualStudio.Threading
private readonly object syncObject = new object();
/// <summary>
/// The source of the task to return from <see cref="WaitAsync"/>.
/// The source of the task to return from <see cref="WaitAsync()"/>.
/// </summary>
/// <devremarks>
/// This should not need the volatile modifier because it is
@ -60,10 +60,10 @@ namespace Microsoft.VisualStudio.Threading
/// </summary>
/// <param name="initialState">A value indicating whether the event should be initially signaled.</param>
/// <param name="allowInliningAwaiters">
/// A value indicating whether to allow <see cref="WaitAsync"/> callers' continuations to execute
/// A value indicating whether to allow <see cref="WaitAsync()"/> callers' continuations to execute
/// on the thread that calls <see cref="SetAsync()"/> before the call returns.
/// <see cref="SetAsync()"/> callers should not hold private locks if this value is <c>true</c> to avoid deadlocks.
/// When <c>false</c>, the task returned from <see cref="WaitAsync"/> may not have fully transitioned to
/// When <c>false</c>, the task returned from <see cref="WaitAsync()"/> may not have fully transitioned to
/// its completed state by the time <see cref="SetAsync()"/> returns to its caller.
/// </param>
public AsyncManualResetEvent(bool initialState = false, bool allowInliningAwaiters = false)
@ -104,7 +104,14 @@ namespace Microsoft.VisualStudio.Threading
}
/// <summary>
/// Sets this event to unblock callers of <see cref="WaitAsync"/>.
/// Returns a task that will be completed when this event is set.
/// </summary>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>A task that completes when the event is set, or cancels with the <paramref name="cancellationToken"/>.</returns>
public Task WaitAsync(CancellationToken cancellationToken) => this.WaitAsync().WithCancellation(cancellationToken);
/// <summary>
/// Sets this event to unblock callers of <see cref="WaitAsync()"/>.
/// </summary>
/// <returns>A task that completes when the signal has been set.</returns>
/// <remarks>
@ -145,7 +152,7 @@ namespace Microsoft.VisualStudio.Threading
}
/// <summary>
/// Sets this event to unblock callers of <see cref="WaitAsync"/>.
/// Sets this event to unblock callers of <see cref="WaitAsync()"/>.
/// </summary>
public void Set()
{
@ -155,7 +162,7 @@ namespace Microsoft.VisualStudio.Threading
}
/// <summary>
/// Resets this event to a state that will block callers of <see cref="WaitAsync"/>.
/// Resets this event to a state that will block callers of <see cref="WaitAsync()"/>.
/// </summary>
public void Reset()
{

Просмотреть файл

@ -37,16 +37,16 @@ namespace Microsoft.VisualStudio.Threading
/// </devnotes>
public partial class AsyncReaderWriterLock : IDisposable
{
/// <summary>
/// The default SynchronizationContext to schedule work after issuing a lock.
/// </summary>
private static readonly SynchronizationContext DefaultSynchronizationContext = new SynchronizationContext();
/// <summary>
/// The object to acquire a Monitor-style lock on for all field access on this instance.
/// </summary>
private readonly object syncObject = new object();
/// <summary>
/// The synchronization context applied to folks who hold upgradeable read and write locks.
/// </summary>
private readonly NonConcurrentSynchronizationContext nonConcurrentSyncContext = new NonConcurrentSynchronizationContext();
/// <summary>
/// A CallContext-local reference to the Awaiter that is on the top of the stack (most recently acquired).
/// </summary>
@ -520,10 +520,6 @@ namespace Microsoft.VisualStudio.Threading
/// <param name="disposing"><c>true</c> if <see cref="Dispose()"/> was called; <c>false</c> if the object is being finalized.</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
this.nonConcurrentSyncContext.Dispose();
}
}
/// <summary>
@ -683,7 +679,7 @@ namespace Microsoft.VisualStudio.Threading
if (this.IsLockHeld(LockKind.Write, awaiter, allowNonLockSupportingContext: true, checkSyncContextCompatibility: false) ||
this.IsLockHeld(LockKind.UpgradeableRead, awaiter, allowNonLockSupportingContext: true, checkSyncContextCompatibility: false))
{
if (SynchronizationContext.Current != this.nonConcurrentSyncContext)
if (!(SynchronizationContext.Current is NonConcurrentSynchronizationContext))
{
// Upgradeable read and write locks *must* have the NonConcurrentSynchronizationContext applied.
return false;
@ -2135,6 +2131,11 @@ namespace Microsoft.VisualStudio.Threading
/// </summary>
private Task releaseAsyncTask;
/// <summary>
/// The synchronization context applied to folks who hold the lock.
/// </summary>
private SynchronizationContext synchronizationContext;
#if DESKTOP || NETSTANDARD2_0
/// <summary>
/// The stacktrace of the caller originally requesting the lock.
@ -2329,7 +2330,7 @@ namespace Microsoft.VisualStudio.Threading
this.lck.ThrowIfUnsupportedThreadOrSyncContext();
if ((this.Kind & (LockKind.UpgradeableRead | LockKind.Write)) != 0)
{
Assumes.True(SynchronizationContext.Current == this.lck.nonConcurrentSyncContext);
Assumes.True(SynchronizationContext.Current is NonConcurrentSynchronizationContext);
}
this.lck.ApplyLockToCallContext(this);
@ -2402,16 +2403,14 @@ namespace Microsoft.VisualStudio.Threading
{
this.continuationAfterLockIssued = continuation;
// Only read locks can be executed trivially. The locks that have some level of exclusivity (upgradeable read and write)
// must be executed via the NonConcurrentSynchronizationContext.
if (this.lck.LockStackContains(LockKind.UpgradeableRead, this) ||
this.lck.LockStackContains(LockKind.Write, this))
var synchronizationContext = this.GetEffectiveSynchronizationContext();
if (this.continuationTaskScheduler != null && synchronizationContext == DefaultSynchronizationContext)
{
this.lck.nonConcurrentSyncContext.Post(state => ((Action)state)(), continuation);
Task.Factory.StartNew(continuation, CancellationToken.None, TaskCreationOptions.PreferFairness, this.continuationTaskScheduler);
}
else
{
Task.Factory.StartNew(continuation, CancellationToken.None, TaskCreationOptions.PreferFairness, this.continuationTaskScheduler ?? TaskScheduler.Default);
synchronizationContext.Post(state => ((Action)state)(), continuation);
}
return true;
@ -2430,6 +2429,52 @@ namespace Microsoft.VisualStudio.Threading
this.fault = ex;
}
/// <summary>
/// Get the correct SynchronizationContext to execute code executing within the lock.
/// Note: we need get the NonConcurrentSynchronizationContext from the nesting exclusive lock, because the child lock is essentially under the same context.
/// When we don't have a valid nesting lock, we will create a new NonConcurrentSynchronizationContext for an exclusive lock. For read lock, we don't put it within a NonConcurrentSynchronizationContext,
/// we set it to DefaultSynchronizationContext to mark we have computed it. The result is cached.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "NonConcurrentSynchronizationContext is shared among locks, and cannot be disposed.")]
private SynchronizationContext GetEffectiveSynchronizationContext()
{
if (this.synchronizationContext == null)
{
// Only read locks can be executed trivially. The locks that have some level of exclusivity (upgradeable read and write)
// must be executed via the NonConcurrentSynchronizationContext.
SynchronizationContext synchronizationContext = null;
Awaiter awaiter = this.NestingLock;
while (awaiter != null)
{
if (this.lck.IsLockActive(awaiter, considerStaActive: true))
{
synchronizationContext = awaiter.GetEffectiveSynchronizationContext();
break;
}
awaiter = awaiter.NestingLock;
}
if (synchronizationContext == null)
{
if (this.kind == LockKind.Read)
{
// We use DefaultSynchronizationContext to indicate that we have already computed the synchronizationContext once, and prevent repeating this logic second time.
synchronizationContext = DefaultSynchronizationContext;
}
else
{
synchronizationContext = new NonConcurrentSynchronizationContext();
}
}
Interlocked.CompareExchange(ref this.synchronizationContext, synchronizationContext, null);
}
return this.synchronizationContext;
}
/// <summary>
/// Responds to lock request cancellation.
/// </summary>

Просмотреть файл

@ -0,0 +1,204 @@
// Copyright (c) PlaceholderCompany. All rights reserved.
namespace Microsoft.VisualStudio.Threading
{
using System;
using System.Threading;
/// <summary>
/// Extensions to <see cref="CancellationToken"/>.
/// </summary>
public static class CancellationTokenExtensions
{
/// <summary>
/// Creates a new <see cref="CancellationToken"/> that is canceled when any of a set of other tokens are canceled.
/// </summary>
/// <param name="original">The first token.</param>
/// <param name="other">The second token.</param>
/// <returns>A struct that contains the combined <see cref="CancellationToken"/> and a means to release memory when you're done using it.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000", Justification = "The CancellationTokenSource is created and returned in a struct responsible for its disposal.")]
public static CombinedCancellationToken CombineWith(this CancellationToken original, CancellationToken other)
{
if (original.IsCancellationRequested || !other.CanBeCanceled)
{
return new CombinedCancellationToken(original);
}
if (other.IsCancellationRequested || !original.CanBeCanceled)
{
return new CombinedCancellationToken(other);
}
// This is the most expensive path to take since it involves allocating memory and requiring disposal.
// Before this point we've checked every condition that would allow us to avoid it.
return new CombinedCancellationToken(CancellationTokenSource.CreateLinkedTokenSource(original, other));
}
/// <summary>
/// Creates a new <see cref="CancellationToken"/> that is canceled when any of a set of other tokens are canceled.
/// </summary>
/// <param name="original">The first token.</param>
/// <param name="others">The additional tokens.</param>
/// <returns>A struct that contains the combined <see cref="CancellationToken"/> and a means to release memory when you're done using it.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000", Justification = "The CancellationTokenSource is created and returned in a struct responsible for its disposal.")]
public static CombinedCancellationToken CombineWith(this CancellationToken original, params CancellationToken[] others)
{
Requires.NotNull(others, nameof(others));
if (original.IsCancellationRequested)
{
return new CombinedCancellationToken(original);
}
int cancelableTokensCount = original.CanBeCanceled ? 1 : 0;
foreach (var other in others)
{
if (other.IsCancellationRequested)
{
return new CombinedCancellationToken(other);
}
if (other.CanBeCanceled)
{
cancelableTokensCount++;
}
}
switch (cancelableTokensCount)
{
case 0:
return new CombinedCancellationToken(CancellationToken.None);
case 1:
if (original.CanBeCanceled)
{
return new CombinedCancellationToken(original);
}
foreach (var other in others)
{
if (other.CanBeCanceled)
{
return new CombinedCancellationToken(other);
}
}
throw Assumes.NotReachable();
case 2:
CancellationToken first = CancellationToken.None;
CancellationToken second = CancellationToken.None;
if (original.CanBeCanceled)
{
first = original;
}
foreach (var other in others)
{
if (other.CanBeCanceled)
{
if (first.CanBeCanceled)
{
second = other;
}
else
{
first = other;
}
}
}
Assumes.True(first.CanBeCanceled && second.CanBeCanceled);
// Call the overload that takes two CancellationTokens explicitly to avoid an array allocation.
return new CombinedCancellationToken(CancellationTokenSource.CreateLinkedTokenSource(first, second));
default:
// This is the most expensive path to take since it involves allocating memory and requiring disposal.
// Before this point we've checked every condition that would allow us to avoid it.
var cancelableTokens = new CancellationToken[cancelableTokensCount];
int i = 0;
foreach (var other in others)
{
if (other.CanBeCanceled)
{
cancelableTokens[i++] = other;
}
}
return new CombinedCancellationToken(CancellationTokenSource.CreateLinkedTokenSource(cancelableTokens));
}
}
/// <summary>
/// Provides access to a <see cref="System.Threading.CancellationToken"/> that combines multiple other tokens,
/// and allows convenient disposal of any applicable <see cref="CancellationTokenSource"/>.
/// </summary>
public readonly struct CombinedCancellationToken : IDisposable, IEquatable<CombinedCancellationToken>
{
/// <summary>
/// The object to dispose when this struct is disposed.
/// </summary>
private readonly CancellationTokenSource cts;
/// <summary>
/// Initializes a new instance of the <see cref="CombinedCancellationToken"/> struct
/// that contains an aggregate <see cref="System.Threading.CancellationToken"/> whose source must be disposed.
/// </summary>
/// <param name="cancellationTokenSource">The cancellation token source.</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062", Justification = "We are prepared to handle null values for CancellationTokenSource.")]
public CombinedCancellationToken(CancellationTokenSource cancellationTokenSource)
{
this.cts = cancellationTokenSource;
this.Token = cancellationTokenSource.Token;
}
/// <summary>
/// Initializes a new instance of the <see cref="CombinedCancellationToken"/> struct
/// that represents just a single, non-disposable <see cref="System.Threading.CancellationToken"/>.
/// </summary>
/// <param name="cancellationToken">The cancellation token</param>
public CombinedCancellationToken(CancellationToken cancellationToken)
{
this.cts = null;
this.Token = cancellationToken;
}
/// <summary>
/// Checks whether two instances of <see cref="CombinedCancellationToken"/> are equal.
/// </summary>
/// <param name="left">The left operand.</param>
/// <param name="right">The right operand.</param>
/// <returns><c>true</c> if they are equal; <c>false</c> otherwise.</returns>
public static bool operator ==(CombinedCancellationToken left, CombinedCancellationToken right) => left.Equals(right);
/// <summary>
/// Checks whether two instances of <see cref="CombinedCancellationToken"/> are not equal.
/// </summary>
/// <param name="left">The left operand.</param>
/// <param name="right">The right operand.</param>
/// <returns><c>true</c> if they are not equal; <c>false</c> if they are equal.</returns>
public static bool operator !=(CombinedCancellationToken left, CombinedCancellationToken right) => !(left == right);
/// <summary>
/// Gets the combined cancellation token.
/// </summary>
public CancellationToken Token { get; }
/// <summary>
/// Disposes the <see cref="CancellationTokenSource"/> behind this combined token, if any.
/// </summary>
public void Dispose()
{
this.cts?.Dispose();
}
/// <inheritdoc />
public override bool Equals(object obj) => obj is CombinedCancellationToken other && this.Equals(other);
/// <inheritdoc />
public bool Equals(CombinedCancellationToken other) => this.cts == other.cts && this.Token.Equals(other.Token);
/// <inheritdoc />
public override int GetHashCode() => (this.cts?.GetHashCode() ?? 0) + this.Token.GetHashCode();
}
}
}

Просмотреть файл

@ -26,7 +26,7 @@ namespace Microsoft.VisualStudio.Threading
/// <param name="dispatcher">The <see cref="Dispatcher"/> that schedules work on the main thread.</param>
/// <param name="priority">
/// The priority with which to schedule any work on the UI thread,
/// when and if <see cref="JoinableTaskFactory.SwitchToMainThreadAsync"/> is called
/// when and if <see cref="JoinableTaskFactory.SwitchToMainThreadAsync(CancellationToken)"/> is called
/// and for each asynchronous return to the main thread after an <c>await</c>.
/// </param>
/// <returns>A <see cref="JoinableTaskFactory"/> that may be used for scheduling async work with the specified priority.</returns>

Просмотреть файл

@ -0,0 +1,49 @@
/********************************************************
* *
* © Copyright (C) Microsoft. All rights reserved. *
* *
*********************************************************/
namespace Microsoft.VisualStudio.Threading
{
/// <summary>
/// Represents a dependent item in the JoinableTask dependency graph, it can be either a <see cref="JoinableTask"/> or a <see cref="JoinableTaskCollection"/>
/// </summary>
internal interface IJoinableTaskDependent
{
/// <summary>
/// Gets the <see cref="Threading.JoinableTaskContext"/> this node belongs to.
/// </summary>
JoinableTaskContext JoinableTaskContext { get; }
/// <summary>
/// Gets a value indicating whether we need reference count child dependent node. This is to keep the current behavior of <see cref="JoinableTaskCollection"/>.
/// </summary>
bool NeedRefCountChildDependencies { get; }
/// <summary>
/// Get the reference of dependent node to record dependencies.
/// </summary>
ref JoinableTaskDependencyGraph.JoinableTaskDependentData GetJoinableTaskDependentData();
/// <summary>
/// A function is called, when this dependent node is added to be a dependency of a parent node.
/// </summary>
void OnAddedToDependency(IJoinableTaskDependent parent);
/// <summary>
/// A function is called, when this dependent node is removed as a dependency of a parent node.
/// </summary>
void OnRemovedFromDependency(IJoinableTaskDependent parentNode);
/// <summary>
/// A function is called, when a dependent child is added.
/// </summary>
void OnDependencyAdded(IJoinableTaskDependent joinChild);
/// <summary>
/// A function is called, when a dependent child is removed.
/// </summary>
void OnDependencyRemoved(IJoinableTaskDependent joinChild);
}
}

Просмотреть файл

@ -1,509 +0,0 @@
/********************************************************
* *
* © Copyright (C) Microsoft. All rights reserved. *
* *
*********************************************************/
namespace Microsoft.VisualStudio.Threading
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
partial class JoinableTask
{
/// <summary>
/// The head of a singly linked list of records to track which task may process events of this task.
/// This list should contain only tasks which need be completed synchronously, and depends on this task.
/// </summary>
private DependentSynchronousTask dependingSynchronousTaskTracking;
/// <summary>
/// Gets a value indicating whether the main thread is waiting for the task's completion
/// </summary>
internal bool HasMainThreadSynchronousTaskWaiting
{
get
{
using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply())
{
lock (this.owner.Context.SyncContextLock)
{
DependentSynchronousTask existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking != null)
{
if ((existingTaskTracking.SynchronousTask.State & JoinableTask.JoinableTaskFlags.SynchronouslyBlockingMainThread) == JoinableTask.JoinableTaskFlags.SynchronouslyBlockingMainThread)
{
return true;
}
existingTaskTracking = existingTaskTracking.Next;
}
return false;
}
}
}
}
/// <summary>
/// Get how many number of synchronous tasks in our tracking list.
/// </summary>
private int CountOfDependingSynchronousTasks()
{
int count = 0;
DependentSynchronousTask existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking != null)
{
count++;
existingTaskTracking = existingTaskTracking.Next;
}
return count;
}
/// <summary>
/// Check whether a task is being tracked in our tracking list.
/// </summary>
private bool IsDependingSynchronousTask(JoinableTask syncTask)
{
DependentSynchronousTask existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking != null)
{
if (existingTaskTracking.SynchronousTask == syncTask)
{
return true;
}
existingTaskTracking = existingTaskTracking.Next;
}
return false;
}
/// <summary>
/// Calculate the collection of events we need trigger after we enqueue a request.
/// </summary>
/// <param name="forMainThread">True if we want to find tasks to process the main thread queue. Otherwise tasks to process the background queue.</param>
/// <returns>The collection of synchronous tasks we need notify.</returns>
private List<JoinableTask> GetDependingSynchronousTasks(bool forMainThread)
{
Assumes.True(Monitor.IsEntered(this.owner.Context.SyncContextLock));
var tasksNeedNotify = new List<JoinableTask>(this.CountOfDependingSynchronousTasks());
DependentSynchronousTask existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking != null)
{
var syncTask = existingTaskTracking.SynchronousTask;
bool syncTaskInOnMainThread = (syncTask.state & JoinableTaskFlags.SynchronouslyBlockingMainThread) == JoinableTaskFlags.SynchronouslyBlockingMainThread;
if (forMainThread == syncTaskInOnMainThread)
{
// Only synchronous tasks are in the list, so we don't need do further check for the CompletingSynchronously flag
tasksNeedNotify.Add(syncTask);
}
existingTaskTracking = existingTaskTracking.Next;
}
return tasksNeedNotify;
}
/// <summary>
/// Applies all synchronous tasks tracked by this task to a new child/dependent task.
/// </summary>
/// <param name="child">The new child task.</param>
/// <returns>Pairs of synchronous tasks we need notify and the event source triggering it, plus the number of pending events.</returns>
private List<PendingNotification> AddDependingSynchronousTaskToChild(JoinableTask child)
{
Requires.NotNull(child, nameof(child));
Assumes.True(Monitor.IsEntered(this.owner.Context.SyncContextLock));
var tasksNeedNotify = new List<PendingNotification>(this.CountOfDependingSynchronousTasks());
DependentSynchronousTask existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking != null)
{
int totalEventNumber = 0;
var eventTriggeringTask = child.AddDependingSynchronousTask(existingTaskTracking.SynchronousTask, ref totalEventNumber);
if (eventTriggeringTask != null)
{
tasksNeedNotify.Add(new PendingNotification(existingTaskTracking.SynchronousTask, eventTriggeringTask, totalEventNumber));
}
existingTaskTracking = existingTaskTracking.Next;
}
return tasksNeedNotify;
}
/// <summary>
/// Removes all synchronous tasks we applies to a dependent task, after the relationship is removed.
/// </summary>
/// <param name="child">The original dependent task</param>
private void RemoveDependingSynchronousTaskFromChild(JoinableTask child)
{
Requires.NotNull(child, nameof(child));
Assumes.True(Monitor.IsEntered(this.owner.Context.SyncContextLock));
DependentSynchronousTask existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking != null)
{
child.RemoveDependingSynchronousTask(existingTaskTracking.SynchronousTask);
existingTaskTracking = existingTaskTracking.Next;
}
}
/// <summary>
/// Get the number of pending messages to be process for the synchronous task.
/// </summary>
/// <param name="task">The synchronous task</param>
/// <returns>The number of events need be processed by the synchronous task in the current JoinableTask.</returns>
private int GetPendingEventCountForTask(JoinableTask task)
{
var queue = ((task.state & JoinableTaskFlags.SynchronouslyBlockingMainThread) == JoinableTaskFlags.SynchronouslyBlockingMainThread)
? this.mainThreadQueue
: this.threadPoolQueue;
return queue != null ? queue.Count : 0;
}
/// <summary>
/// Tracks a new synchronous task for this task.
/// A synchronous task is a task blocking a thread and waits it to be completed. We may want the blocking thread
/// to process events from this task.
/// </summary>
/// <param name="task">The synchronous task</param>
/// <param name="totalEventsPending">The total events need be processed</param>
/// <returns>The task causes us to trigger the event of the synchronous task, so it can process new events. Null means we don't need trigger any event</returns>
private JoinableTask AddDependingSynchronousTask(JoinableTask task, ref int totalEventsPending)
{
Requires.NotNull(task, nameof(task));
Assumes.True(Monitor.IsEntered(this.owner.Context.SyncContextLock));
if (this.IsCompleted)
{
return null;
}
if (this.IsCompleteRequested)
{
// A completed task might still have pending items in the queue.
int pendingCount = this.GetPendingEventCountForTask(task);
if (pendingCount > 0)
{
totalEventsPending += pendingCount;
return this;
}
return null;
}
DependentSynchronousTask existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking != null)
{
if (existingTaskTracking.SynchronousTask == task)
{
existingTaskTracking.ReferenceCount++;
return null;
}
existingTaskTracking = existingTaskTracking.Next;
}
int pendingItemCount = this.GetPendingEventCountForTask(task);
JoinableTask eventTriggeringTask = null;
if (pendingItemCount > 0)
{
totalEventsPending += pendingItemCount;
eventTriggeringTask = this;
}
// For a new synchronous task, we need apply it to our child tasks.
DependentSynchronousTask newTaskTracking = new DependentSynchronousTask(task)
{
Next = this.dependingSynchronousTaskTracking,
};
this.dependingSynchronousTaskTracking = newTaskTracking;
if (this.childOrJoinedJobs != null)
{
foreach (var item in this.childOrJoinedJobs)
{
var childTiggeringTask = item.Key.AddDependingSynchronousTask(task, ref totalEventsPending);
if (eventTriggeringTask == null)
{
eventTriggeringTask = childTiggeringTask;
}
}
}
return eventTriggeringTask;
}
/// <summary>
/// Remove all synchronous tasks tracked by the this task.
/// This is called when this task is completed
/// </summary>
private void CleanupDependingSynchronousTask()
{
if (this.dependingSynchronousTaskTracking != null)
{
DependentSynchronousTask existingTaskTracking = this.dependingSynchronousTaskTracking;
this.dependingSynchronousTaskTracking = null;
if (this.childOrJoinedJobs != null)
{
var childrenTasks = this.childOrJoinedJobs.Select(item => item.Key).ToList();
while (existingTaskTracking != null)
{
RemoveDependingSynchronousTaskFrom(childrenTasks, existingTaskTracking.SynchronousTask, false);
existingTaskTracking = existingTaskTracking.Next;
}
}
}
}
/// <summary>
/// Remove a synchronous task from the tracking list.
/// </summary>
/// <param name="task">The synchronous task</param>
/// <param name="force">We always remove it from the tracking list if it is true. Otherwise, we keep tracking the reference count.</param>
private void RemoveDependingSynchronousTask(JoinableTask task, bool force = false)
{
Requires.NotNull(task, nameof(task));
Assumes.True(Monitor.IsEntered(this.owner.Context.SyncContextLock));
if (task.dependingSynchronousTaskTracking != null)
{
RemoveDependingSynchronousTaskFrom(new JoinableTask[] { this }, task, force);
}
}
/// <summary>
/// Remove a synchronous task from the tracking list of a list of tasks.
/// </summary>
/// <param name="tasks">A list of tasks we need update the tracking list.</param>
/// <param name="syncTask">The synchronous task we want to remove</param>
/// <param name="force">We always remove it from the tracking list if it is true. Otherwise, we keep tracking the reference count.</param>
private static void RemoveDependingSynchronousTaskFrom(IReadOnlyList<JoinableTask> tasks, JoinableTask syncTask, bool force)
{
Requires.NotNull(tasks, nameof(tasks));
Requires.NotNull(syncTask, nameof(syncTask));
HashSet<JoinableTask> reachableTasks = null;
HashSet<JoinableTask> remainTasks = null;
if (force)
{
reachableTasks = new HashSet<JoinableTask>();
}
foreach (var task in tasks)
{
task.RemoveDependingSynchronousTask(syncTask, reachableTasks, ref remainTasks);
}
if (!force && remainTasks != null && remainTasks.Count > 0)
{
// a set of tasks may form a dependent loop, so it will make the reference count system
// not to work correctly when we try to remove the synchronous task.
// To get rid of those loops, if a task still tracks the synchronous task after reducing
// the reference count, we will calculate the entire reachable tree from the root. That will
// tell us the exactly tasks which need track the synchronous task, and we will clean up the rest.
reachableTasks = new HashSet<JoinableTask>();
syncTask.ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks(reachableTasks, remainTasks);
// force to remove all invalid items
HashSet<JoinableTask> remainPlaceHold = null;
foreach (var remainTask in remainTasks)
{
remainTask.RemoveDependingSynchronousTask(syncTask, reachableTasks, ref remainPlaceHold);
}
}
}
/// <summary>
/// Compute all reachable tasks from a synchronous task. Because we use the result to clean up invalid
/// items from the remain task, we will remove valid task from the collection, and stop immediately if nothing is left.
/// </summary>
/// <param name="reachableTasks">All reachable tasks. This is not a completed list, if there is no remain task.</param>
/// <param name="remainTasks">The remain tasks we want to check. After the execution, it will retain non-reachable tasks.</param>
private void ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks(HashSet<JoinableTask> reachableTasks, HashSet<JoinableTask> remainTasks)
{
Requires.NotNull(remainTasks, nameof(remainTasks));
Requires.NotNull(reachableTasks, nameof(reachableTasks));
if (!this.IsCompleted)
{
if (reachableTasks.Add(this))
{
if (remainTasks.Remove(this) && reachableTasks.Count == 0)
{
// no remain task left, quit the loop earlier
return;
}
if (this.childOrJoinedJobs != null)
{
foreach (var item in this.childOrJoinedJobs)
{
item.Key.ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks(reachableTasks, remainTasks);
}
}
}
}
}
/// <summary>
/// Remove a synchronous task from the tracking list of this task.
/// </summary>
/// <param name="task">The synchronous task need be removed</param>
/// <param name="reachableTasks">
/// If it is not null, it will contain all task which can track the synchronous task. We will ignore reference count in that case.
/// </param>
/// <param name="remainingDependentTasks">This will retain the tasks which still tracks the synchronous task.</param>
private void RemoveDependingSynchronousTask(JoinableTask task, HashSet<JoinableTask> reachableTasks, ref HashSet<JoinableTask> remainingDependentTasks)
{
Requires.NotNull(task, nameof(task));
DependentSynchronousTask previousTaskTracking = null;
DependentSynchronousTask currentTaskTracking = this.dependingSynchronousTaskTracking;
bool removed = false;
while (currentTaskTracking != null)
{
if (currentTaskTracking.SynchronousTask == task)
{
if (--currentTaskTracking.ReferenceCount > 0)
{
if (reachableTasks != null)
{
if (!reachableTasks.Contains(this))
{
currentTaskTracking.ReferenceCount = 0;
}
}
}
if (currentTaskTracking.ReferenceCount == 0)
{
removed = true;
if (previousTaskTracking != null)
{
previousTaskTracking.Next = currentTaskTracking.Next;
}
else
{
this.dependingSynchronousTaskTracking = currentTaskTracking.Next;
}
}
if (reachableTasks == null)
{
if (removed)
{
if (remainingDependentTasks != null)
{
remainingDependentTasks.Remove(this);
}
}
else
{
if (remainingDependentTasks == null)
{
remainingDependentTasks = new HashSet<JoinableTask>();
}
remainingDependentTasks.Add(this);
}
}
break;
}
previousTaskTracking = currentTaskTracking;
currentTaskTracking = currentTaskTracking.Next;
}
if (removed && this.childOrJoinedJobs != null)
{
foreach (var item in this.childOrJoinedJobs)
{
item.Key.RemoveDependingSynchronousTask(task, reachableTasks, ref remainingDependentTasks);
}
}
}
/// <summary>
/// The record of a pending notification we need send to the synchronous task that we have some new messages to process.
/// </summary>
private struct PendingNotification
{
private readonly JoinableTask synchronousTask;
private readonly JoinableTask taskHasPendingMessages;
private readonly int newPendingMessagesCount;
public PendingNotification(JoinableTask synchronousTask, JoinableTask taskHasPendingMessages, int newPendingMessagesCount)
{
Requires.NotNull(synchronousTask, nameof(synchronousTask));
Requires.NotNull(taskHasPendingMessages, nameof(taskHasPendingMessages));
this.synchronousTask = synchronousTask;
this.taskHasPendingMessages = taskHasPendingMessages;
this.newPendingMessagesCount = newPendingMessagesCount;
}
/// <summary>
/// Gets the synchronous task which need process new messages.
/// </summary>
public JoinableTask SynchronousTask
{
get { return this.synchronousTask; }
}
/// <summary>
/// Gets one JoinableTask which may have pending messages. We may have multiple new JoinableTasks which contains pending messages.
/// This is just one of them. It gives the synchronous task a way to start quickly without searching all messages.
/// </summary>
public JoinableTask TaskHasPendingMessages
{
get { return this.taskHasPendingMessages; }
}
/// <summary>
/// Gets the total number of new pending messages. The real number could be less than that, but should not be more than that.
/// </summary>
public int NewPendingMessagesCount
{
get { return this.newPendingMessagesCount; }
}
}
/// <summary>
/// A single linked list to maintain synchronous JoinableTask depends on the current task,
/// which may process the queue of the current task.
/// </summary>
private class DependentSynchronousTask
{
public DependentSynchronousTask(JoinableTask task)
{
this.SynchronousTask = task;
this.ReferenceCount = 1;
}
/// <summary>
/// Gets or sets the chain of the single linked list
/// </summary>
internal DependentSynchronousTask Next { get; set; }
/// <summary>
/// Gets the synchronous task
/// </summary>
internal JoinableTask SynchronousTask { get; private set; }
/// <summary>
/// Gets or sets the reference count. We remove the item from the list, if it reaches 0.
/// </summary>
internal int ReferenceCount { get; set; }
}
}
}

Просмотреть файл

@ -23,10 +23,10 @@ namespace Microsoft.VisualStudio.Threading
/// deadlocks while synchronously blocking the Main thread for the operation's completion.
/// </summary>
/// <remarks>
/// For more complete comments please see the <see cref="JoinableTaskContext"/>.
/// For more complete comments please see the <see cref="Threading.JoinableTaskContext"/>.
/// </remarks>
[DebuggerDisplay("IsCompleted: {IsCompleted}, Method = {EntryMethodInfo != null ? EntryMethodInfo.Name : null}")]
public partial class JoinableTask
public partial class JoinableTask : IJoinableTaskDependent
{
/// <summary>
/// Stores the top-most JoinableTask that is completing on the current thread, if any.
@ -34,7 +34,7 @@ namespace Microsoft.VisualStudio.Threading
private static readonly ThreadLocal<JoinableTask> CompletingTask = new ThreadLocal<JoinableTask>();
/// <summary>
/// The <see cref="JoinableTaskContext"/> that began the async operation.
/// The <see cref="Threading.JoinableTaskContext"/> that began the async operation.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly JoinableTaskFactory owner;
@ -52,30 +52,29 @@ namespace Microsoft.VisualStudio.Threading
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private ListOfOftenOne<JoinableTaskFactory> nestingFactories;
/// <summary>
/// The <see cref="JoinableTaskDependencyGraph.JoinableTaskDependentData"/> to track dependencies between tasks.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private JoinableTaskDependencyGraph.JoinableTaskDependentData dependentData;
/// <summary>
/// The collections that this job is a member of.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private ListOfOftenOne<JoinableTaskCollection> collectionMembership;
private ListOfOftenOne<IJoinableTaskDependent> dependencyParents;
/// <summary>
/// The Task returned by the async delegate that this JoinableTask originally executed.
/// The <see cref="System.Threading.Tasks.Task"/> returned by the async delegate that this JoinableTask originally executed,
/// or a <see cref="TaskCompletionSource{TResult}"/> if the <see cref="Task"/> property was observed before <see cref="initialDelegate"/>
/// had given us a Task.
/// </summary>
/// <value>
/// This is <c>null</c> until after the async delegate returns a Task,
/// This is <c>null</c> until after <see cref="initialDelegate"/> returns a <see cref="Task"/> (or the <see cref="Task"/> property is observed),
/// and retains its value even after this JoinableTask completes.
/// </value>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private Task wrappedTask;
/// <summary>
/// A map of jobs that we should be willing to dequeue from when we control the UI thread, and a ref count. Lazily constructed.
/// </summary>
/// <remarks>
/// When the value in an entry is decremented to 0, the entry is removed from the map.
/// </remarks>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private WeakKeyDictionary<JoinableTask, int> childOrJoinedJobs;
private object wrappedTask;
/// <summary>
/// An event that is signaled when any queue in the dependent has item to process. Lazily constructed.
@ -104,7 +103,7 @@ namespace Microsoft.VisualStudio.Threading
private ExecutionQueue threadPoolQueue;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private JoinableTaskFlags state;
private volatile JoinableTaskFlags state;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private JoinableTaskSynchronizationContext mainThreadJobSyncContext;
@ -120,6 +119,11 @@ namespace Microsoft.VisualStudio.Threading
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private Delegate initialDelegate;
/// <summary>
/// Backing field for the <see cref="WeakSelf"/> property.
/// </summary>
private WeakReference<JoinableTask> weakSelf;
/// <summary>
/// Initializes a new instance of the <see cref="JoinableTask"/> class.
/// </summary>
@ -170,7 +174,7 @@ namespace Microsoft.VisualStudio.Threading
StartedOnMainThread = 0x2,
/// <summary>
/// This task has had its Complete method called, but has lingering continuations to execute.
/// This task has had its Complete method called, but may have lingering continuations to execute.
/// </summary>
CompleteRequested = 0x4,
@ -191,14 +195,19 @@ namespace Microsoft.VisualStudio.Threading
SynchronouslyBlockingMainThread = 0x20,
}
/// <summary>
/// Gets JoinableTaskContext for <see cref="JoinableTaskContextNode"/> to access locks.
/// </summary>
private JoinableTaskContext JoinableTaskContext => this.owner.Context;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private Task QueueNeedProcessEvent
{
get
{
using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply())
using (this.JoinableTaskContext.NoMessagePumpSynchronizationContext.Apply())
{
lock (this.owner.Context.SyncContextLock)
lock (this.JoinableTaskContext.SyncContextLock)
{
if (this.queueNeedProcessEvent == null)
{
@ -231,9 +240,9 @@ namespace Microsoft.VisualStudio.Threading
{
get
{
using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply())
using (this.JoinableTaskContext.NoMessagePumpSynchronizationContext.Apply())
{
lock (this.owner.Context.SyncContextLock)
lock (this.JoinableTaskContext.SyncContextLock)
{
if (!this.IsCompleteRequested)
{
@ -263,19 +272,30 @@ namespace Microsoft.VisualStudio.Threading
{
get
{
using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply())
if (this.wrappedTask == null)
{
lock (this.owner.Context.SyncContextLock)
using (this.JoinableTaskContext.NoMessagePumpSynchronizationContext.Apply())
{
// If this assumes ever fails, we need to add the ability to synthesize a task
// that we'll complete when the wrapped task that we eventually are assigned completes.
Assumes.NotNull(this.wrappedTask);
return this.wrappedTask;
lock (this.JoinableTaskContext.SyncContextLock)
{
if (this.wrappedTask == null)
{
// We'd rather not do this. The field is assigned elsewhere later on if we haven't hit this first.
// But some caller needs a Task that we don't yet have, so we have to spin one up.
this.wrappedTask = this.CreateTaskCompletionSource();
}
}
}
}
return this.wrappedTask as Task ?? this.GetTaskFromCompletionSource(this.wrappedTask);
}
}
JoinableTaskContext IJoinableTaskDependent.JoinableTaskContext => this.JoinableTaskContext;
bool IJoinableTaskDependent.NeedRefCountChildDependencies => true;
/// <summary>
/// Gets the JoinableTask that is completing (i.e. synchronously blocking) on this thread, nearest to the top of the callstack.
/// </summary>
@ -297,38 +317,71 @@ namespace Microsoft.VisualStudio.Threading
{
get
{
using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply())
if (this.JoinableTaskContext.IsOnMainThread)
{
lock (this.owner.Context.SyncContextLock)
if (this.mainThreadJobSyncContext == null)
{
if (this.Factory.Context.IsOnMainThread)
using (this.JoinableTaskContext.NoMessagePumpSynchronizationContext.Apply())
{
if (this.mainThreadJobSyncContext == null)
lock (this.JoinableTaskContext.SyncContextLock)
{
this.mainThreadJobSyncContext = new JoinableTaskSynchronizationContext(this, true);
}
return this.mainThreadJobSyncContext;
}
else
{
if (this.SynchronouslyBlockingThreadPool)
{
if (this.threadPoolJobSyncContext == null)
if (this.mainThreadJobSyncContext == null)
{
this.threadPoolJobSyncContext = new JoinableTaskSynchronizationContext(this, false);
this.mainThreadJobSyncContext = new JoinableTaskSynchronizationContext(this, true);
}
return this.threadPoolJobSyncContext;
}
else
{
// If we're not blocking the threadpool, there is no reason to use a thread pool sync context.
return null;
}
}
}
return this.mainThreadJobSyncContext;
}
else
{
// This property only changes from true to false, and it reads a volatile field.
// To avoid (measured) lock contention, we skip the lock, risking that we could potentially
// enter the true block a little more than if we took a lock. But returning a synccontext
// for task whose completion was requested is a safe operation, since every sync context we return
// must be operable after that point anyway.
if (this.SynchronouslyBlockingThreadPool)
{
if (this.threadPoolJobSyncContext == null)
{
using (this.JoinableTaskContext.NoMessagePumpSynchronizationContext.Apply())
{
lock (this.JoinableTaskContext.SyncContextLock)
{
if (this.threadPoolJobSyncContext == null)
{
this.threadPoolJobSyncContext = new JoinableTaskSynchronizationContext(this, false);
}
}
}
}
return this.threadPoolJobSyncContext;
}
else
{
// If we're not blocking the threadpool, there is no reason to use a thread pool sync context.
return null;
}
}
}
}
/// <summary>
/// Gets a weak reference to this object.
/// </summary>
internal WeakReference<JoinableTask> WeakSelf
{
get
{
if (this.weakSelf == null)
{
this.weakSelf = new WeakReference<JoinableTask>(this);
}
return this.weakSelf;
}
}
@ -364,31 +417,12 @@ namespace Microsoft.VisualStudio.Threading
{
get
{
Assumes.True(Monitor.IsEntered(this.owner.Context.SyncContextLock));
Assumes.True(Monitor.IsEntered(this.JoinableTaskContext.SyncContextLock));
return (this.mainThreadQueue != null && this.mainThreadQueue.Count > 0)
|| (this.threadPoolQueue != null && this.threadPoolQueue.Count > 0);
}
}
/// <summary>
/// Gets a snapshot of all joined tasks.
/// FOR DIAGNOSTICS COLLECTION ONLY.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal IEnumerable<JoinableTask> ChildOrJoinedJobs
{
get
{
Assumes.True(Monitor.IsEntered(this.owner.Context.SyncContextLock));
if (this.childOrJoinedJobs == null)
{
return Enumerable.Empty<JoinableTask>();
}
return this.childOrJoinedJobs.Select(p => p.Key).ToArray();
}
}
/// <summary>
/// Gets a snapshot of all work queued to the main thread.
/// FOR DIAGNOSTICS COLLECTION ONLY.
@ -398,7 +432,7 @@ namespace Microsoft.VisualStudio.Threading
{
get
{
Assumes.True(Monitor.IsEntered(this.owner.Context.SyncContextLock));
Assumes.True(Monitor.IsEntered(this.JoinableTaskContext.SyncContextLock));
if (this.mainThreadQueue == null)
{
return Enumerable.Empty<SingleExecuteProtector>();
@ -417,7 +451,7 @@ namespace Microsoft.VisualStudio.Threading
{
get
{
Assumes.True(Monitor.IsEntered(this.owner.Context.SyncContextLock));
Assumes.True(Monitor.IsEntered(this.JoinableTaskContext.SyncContextLock));
if (this.threadPoolQueue == null)
{
return Enumerable.Empty<SingleExecuteProtector>();
@ -436,8 +470,8 @@ namespace Microsoft.VisualStudio.Threading
{
get
{
Assumes.True(Monitor.IsEntered(this.owner.Context.SyncContextLock));
return this.collectionMembership.ToArray();
Assumes.True(Monitor.IsEntered(this.JoinableTaskContext.SyncContextLock));
return this.dependencyParents.OfType<JoinableTaskCollection>().ToList();
}
}
@ -447,7 +481,7 @@ namespace Microsoft.VisualStudio.Threading
/// Gets or sets a value indicating whether this task has had its Complete() method called..
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private bool IsCompleteRequested
internal bool IsCompleteRequested
{
get
{
@ -466,9 +500,10 @@ namespace Microsoft.VisualStudio.Threading
{
get
{
return (this.state & JoinableTaskFlags.StartedSynchronously) == JoinableTaskFlags.StartedSynchronously
&& (this.state & JoinableTaskFlags.StartedOnMainThread) != JoinableTaskFlags.StartedOnMainThread
&& (this.state & JoinableTaskFlags.CompleteRequested) != JoinableTaskFlags.CompleteRequested;
JoinableTaskFlags state = this.state;
return (state & JoinableTaskFlags.StartedSynchronously) == JoinableTaskFlags.StartedSynchronously
&& (state & JoinableTaskFlags.StartedOnMainThread) != JoinableTaskFlags.StartedOnMainThread
&& (state & JoinableTaskFlags.CompleteRequested) != JoinableTaskFlags.CompleteRequested;
}
}
@ -477,9 +512,10 @@ namespace Microsoft.VisualStudio.Threading
{
get
{
return (this.state & JoinableTaskFlags.StartedSynchronously) == JoinableTaskFlags.StartedSynchronously
&& (this.state & JoinableTaskFlags.StartedOnMainThread) == JoinableTaskFlags.StartedOnMainThread
&& (this.state & JoinableTaskFlags.CompleteRequested) != JoinableTaskFlags.CompleteRequested;
JoinableTaskFlags state = this.state;
return (state & JoinableTaskFlags.StartedSynchronously) == JoinableTaskFlags.StartedSynchronously
&& (state & JoinableTaskFlags.StartedOnMainThread) == JoinableTaskFlags.StartedOnMainThread
&& (state & JoinableTaskFlags.CompleteRequested) != JoinableTaskFlags.CompleteRequested;
}
}
@ -538,7 +574,7 @@ namespace Microsoft.VisualStudio.Threading
internal void Post(SendOrPostCallback d, object state, bool mainThreadAffinitized)
{
using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply())
using (this.JoinableTaskContext.NoMessagePumpSynchronizationContext.Apply())
{
SingleExecuteProtector wrapper = null;
List<AsyncManualResetEvent> eventsNeedNotify = null; // initialized if we should pulse it at the end of the method
@ -546,7 +582,7 @@ namespace Microsoft.VisualStudio.Threading
bool isCompleteRequested;
bool synchronouslyBlockingMainThread;
lock (this.owner.Context.SyncContextLock)
lock (this.JoinableTaskContext.SyncContextLock)
{
isCompleteRequested = this.IsCompleteRequested;
synchronouslyBlockingMainThread = this.SynchronouslyBlockingMainThread;
@ -574,7 +610,7 @@ namespace Microsoft.VisualStudio.Threading
wrapper.RaiseTransitioningEvents();
}
lock (this.owner.Context.SyncContextLock)
lock (this.JoinableTaskContext.SyncContextLock)
{
if (mainThreadAffinitized)
{
@ -612,7 +648,7 @@ namespace Microsoft.VisualStudio.Threading
if (mainThreadQueueUpdated || backgroundThreadQueueUpdated)
{
var tasksNeedNotify = this.GetDependingSynchronousTasks(mainThreadQueueUpdated);
var tasksNeedNotify = JoinableTaskDependencyGraph.GetDependingSynchronousTasks(this, mainThreadQueueUpdated);
if (tasksNeedNotify.Count > 0)
{
eventsNeedNotify = new List<AsyncManualResetEvent>(tasksNeedNotify.Count);
@ -620,7 +656,7 @@ namespace Microsoft.VisualStudio.Threading
{
if (taskToNotify.pendingEventSource == null || taskToNotify == this)
{
taskToNotify.pendingEventSource = new WeakReference<JoinableTask>(this);
taskToNotify.pendingEventSource = this.WeakSelf;
}
taskToNotify.pendingEventCount++;
@ -675,26 +711,52 @@ namespace Microsoft.VisualStudio.Threading
return this.JoinAsync().GetAwaiter();
}
/// <summary>
/// Instantiate a <see cref="TaskCompletionSourceWithoutInlining{T}"/> that can track the ultimate result of <see cref="initialDelegate" />.
/// </summary>
/// <returns>The new task completion source.</returns>
/// <remarks>
/// The implementation should be sure to instantiate a <see cref="TaskCompletionSource{TResult}"/> that will
/// NOT inline continuations, since we'll be completing this ourselves, potentially while holding a private lock.
/// </remarks>
internal virtual object CreateTaskCompletionSource() => new TaskCompletionSourceWithoutInlining<EmptyStruct>(allowInliningContinuations: false);
/// <summary>
/// Retrieves the <see cref="TaskCompletionSourceWithoutInlining{T}.Task"/> from a <see cref="TaskCompletionSourceWithoutInlining{T}"/>.
/// </summary>
/// <param name="taskCompletionSource">The task completion source.</param>
/// <returns>The <see cref="System.Threading.Tasks.Task"/> that will complete with this <see cref="TaskCompletionSourceWithoutInlining{T}"/>.</returns>
internal virtual Task GetTaskFromCompletionSource(object taskCompletionSource) => ((TaskCompletionSourceWithoutInlining<EmptyStruct>)taskCompletionSource).Task;
/// <summary>
/// Completes a <see cref="TaskCompletionSourceWithoutInlining{T}"/>
/// </summary>
/// <param name="wrappedTask">The task to read a result from.</param>
/// <param name="taskCompletionSource">The <see cref="TaskCompletionSourceWithoutInlining{T}"/> created earlier with <see cref="CreateTaskCompletionSource()"/> to apply the result to.</param>
internal virtual void CompleteTaskSourceFromWrappedTask(Task wrappedTask, object taskCompletionSource) => wrappedTask.ApplyResultTo((TaskCompletionSourceWithoutInlining<EmptyStruct>)taskCompletionSource);
internal void SetWrappedTask(Task wrappedTask)
{
Requires.NotNull(wrappedTask, nameof(wrappedTask));
using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply())
using (this.JoinableTaskContext.NoMessagePumpSynchronizationContext.Apply())
{
lock (this.owner.Context.SyncContextLock)
lock (this.JoinableTaskContext.SyncContextLock)
{
Assumes.Null(this.wrappedTask);
this.wrappedTask = wrappedTask;
if (this.wrappedTask == null)
{
this.wrappedTask = wrappedTask;
}
if (wrappedTask.IsCompleted)
{
this.Complete();
this.Complete(wrappedTask);
}
else
{
// Arrange for the wrapped task to complete this job when the task completes.
this.wrappedTask.ContinueWith(
(t, s) => ((JoinableTask)s).Complete(),
wrappedTask.ContinueWith(
(t, s) => ((JoinableTask)s).Complete(t),
this,
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
@ -707,12 +769,20 @@ namespace Microsoft.VisualStudio.Threading
/// <summary>
/// Fires when the underlying Task is completed.
/// </summary>
internal void Complete()
/// <param name="wrappedTask">The actual result from <see cref="initialDelegate"/>.</param>
internal void Complete(Task wrappedTask)
{
using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply())
// If we had to synthesize a Task earlier, then wrappedTask is a TaskCompletionSource,
// which we should now complete.
if (!(this.wrappedTask is Task))
{
this.CompleteTaskSourceFromWrappedTask(wrappedTask, this.wrappedTask);
}
using (this.JoinableTaskContext.NoMessagePumpSynchronizationContext.Apply())
{
AsyncManualResetEvent queueNeedProcessEvent = null;
lock (this.owner.Context.SyncContextLock)
lock (this.JoinableTaskContext.SyncContextLock)
{
if (!this.IsCompleteRequested)
{
@ -734,7 +804,7 @@ namespace Microsoft.VisualStudio.Threading
// will likely want to know that the JoinableTask has completed.
queueNeedProcessEvent = this.queueNeedProcessEvent;
this.CleanupDependingSynchronousTask();
JoinableTaskDependencyGraph.OnTaskCompleted(this);
}
}
@ -746,52 +816,6 @@ namespace Microsoft.VisualStudio.Threading
}
}
internal void RemoveDependency(JoinableTask joinChild)
{
Requires.NotNull(joinChild, nameof(joinChild));
using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply())
{
lock (this.owner.Context.SyncContextLock)
{
if (this.childOrJoinedJobs != null && this.childOrJoinedJobs.TryGetValue(joinChild, out int refCount))
{
if (refCount == 1)
{
this.childOrJoinedJobs.Remove(joinChild);
this.RemoveDependingSynchronousTaskFromChild(joinChild);
}
else
{
this.childOrJoinedJobs[joinChild] = --refCount;
}
}
}
}
}
/// <summary>
/// Recursively adds this joinable and all its dependencies to the specified set, that are not yet completed.
/// </summary>
internal void AddSelfAndDescendentOrJoinedJobs(HashSet<JoinableTask> joinables)
{
Requires.NotNull(joinables, nameof(joinables));
if (!this.IsCompleted)
{
if (joinables.Add(this))
{
if (this.childOrJoinedJobs != null)
{
foreach (var item in this.childOrJoinedJobs)
{
item.Key.AddSelfAndDescendentOrJoinedJobs(joinables);
}
}
}
}
}
/// <summary>Runs a loop to process all queued work items, returning only when the task is completed.</summary>
internal void CompleteOnCurrentThread()
{
@ -804,7 +828,7 @@ namespace Microsoft.VisualStudio.Threading
{
bool onMainThread = false;
var additionalFlags = JoinableTaskFlags.CompletingSynchronously;
if (this.owner.Context.IsOnMainThread)
if (this.JoinableTaskContext.IsOnMainThread)
{
additionalFlags |= JoinableTaskFlags.SynchronouslyBlockingMainThread;
onMainThread = true;
@ -819,20 +843,20 @@ namespace Microsoft.VisualStudio.Threading
ThreadingEventSource.Instance.CompleteOnCurrentThreadStart(this.GetHashCode(), onMainThread);
}
using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply())
using (this.JoinableTaskContext.NoMessagePumpSynchronizationContext.Apply())
{
lock (this.owner.Context.SyncContextLock)
lock (this.JoinableTaskContext.SyncContextLock)
{
this.pendingEventCount = 0;
JoinableTaskDependencyGraph.OnSynchronousTaskStartToBlockWaiting(this, out JoinableTask pendingRequestTask, out this.pendingEventCount);
// Add the task to the depending tracking list of itself, so it will monitor the event queue.
this.pendingEventSource = new WeakReference<JoinableTask>(this.AddDependingSynchronousTask(this, ref this.pendingEventCount));
this.pendingEventSource = pendingRequestTask?.WeakSelf;
}
}
if (onMainThread)
{
this.owner.Context.OnSynchronousJoinableTaskToCompleteOnMainThread(this);
this.JoinableTaskContext.OnSynchronousJoinableTaskToCompleteOnMainThread(this);
}
try
@ -840,7 +864,7 @@ namespace Microsoft.VisualStudio.Threading
// Don't use IsCompleted as the condition because that
// includes queues of posted work that don't have to complete for the
// JoinableTask to be ready to return from the JTF.Run method.
HashSet<JoinableTask> visited = null;
HashSet<IJoinableTaskDependent> visited = null;
while (!this.IsCompleteRequested)
{
if (this.TryDequeueSelfOrDependencies(onMainThread, ref visited, out SingleExecuteProtector work, out Task tryAgainAfter))
@ -858,14 +882,7 @@ namespace Microsoft.VisualStudio.Threading
}
finally
{
using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply())
{
lock (this.owner.Context.SyncContextLock)
{
// Remove itself from the tracking list, after the task is completed.
this.RemoveDependingSynchronousTask(this, true);
}
}
JoinableTaskDependencyGraph.OnSynchronousTaskEndToBlockWaiting(this);
}
if (ThreadingEventSource.Instance.IsEnabled())
@ -877,7 +894,7 @@ namespace Microsoft.VisualStudio.Threading
{
if (onMainThread)
{
this.owner.Context.OnSynchronousJoinableTaskToCompleteOnMainThread(this);
this.JoinableTaskContext.OnSynchronousJoinableTaskToCompleteOnMainThread(this);
}
}
@ -907,11 +924,11 @@ namespace Microsoft.VisualStudio.Threading
{
// Note this code may execute more than once, as multiple queue completion
// notifications come in.
this.owner.Context.OnJoinableTaskCompleted(this);
this.JoinableTaskContext.OnJoinableTaskCompleted(this);
foreach (var collection in this.collectionMembership)
foreach (var collection in this.dependencyParents)
{
collection.Remove(this);
JoinableTaskDependencyGraph.RemoveDependency(collection, this);
}
if (this.mainThreadJobSyncContext != null)
@ -930,41 +947,70 @@ namespace Microsoft.VisualStudio.Threading
}
}
internal void OnAddedToCollection(JoinableTaskCollection collection)
ref JoinableTaskDependencyGraph.JoinableTaskDependentData IJoinableTaskDependent.GetJoinableTaskDependentData()
{
Requires.NotNull(collection, nameof(collection));
this.collectionMembership.Add(collection);
return ref this.dependentData;
}
internal void OnRemovedFromCollection(JoinableTaskCollection collection)
void IJoinableTaskDependent.OnAddedToDependency(IJoinableTaskDependent parentNode)
{
Requires.NotNull(parentNode, nameof(parentNode));
this.dependencyParents.Add(parentNode);
}
void IJoinableTaskDependent.OnRemovedFromDependency(IJoinableTaskDependent parentNode)
{
Requires.NotNull(parentNode, nameof(parentNode));
this.dependencyParents.Remove(parentNode);
}
void IJoinableTaskDependent.OnDependencyAdded(IJoinableTaskDependent joinChild)
{
}
void IJoinableTaskDependent.OnDependencyRemoved(IJoinableTaskDependent joinChild)
{
Requires.NotNull(collection, nameof(collection));
this.collectionMembership.Remove(collection);
}
/// <summary>
/// Adds the specified flags to the <see cref="state"/> field.
/// Get the number of pending messages to be process for the synchronous task.
/// </summary>
private void AddStateFlags(JoinableTaskFlags flags)
/// <param name="synchronousTask">The synchronous task</param>
/// <returns>The number of events need be processed by the synchronous task in the current JoinableTask.</returns>
internal int GetPendingEventCountForSynchronousTask(JoinableTask synchronousTask)
{
// Try to avoid taking a lock if the flags are already set appropriately.
if ((this.state & flags) != flags)
{
using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply())
{
lock (this.owner.Context.SyncContextLock)
{
this.state |= flags;
}
}
}
Requires.NotNull(synchronousTask, nameof(synchronousTask));
var queue = ((synchronousTask.state & JoinableTaskFlags.SynchronouslyBlockingMainThread) == JoinableTaskFlags.SynchronouslyBlockingMainThread)
? this.mainThreadQueue
: this.threadPoolQueue;
return queue != null ? queue.Count : 0;
}
private bool TryDequeueSelfOrDependencies(bool onMainThread, ref HashSet<JoinableTask> visited, out SingleExecuteProtector work, out Task tryAgainAfter)
/// <summary>
/// This is a helper method to parepare notifing the sychronous task for pending events.
/// It must be called inside JTF lock, and returns a collection of event to trigger later. (Those events must be triggered out of the JTF lock.)
/// </summary>
internal AsyncManualResetEvent RegisterPendingEventsForSynchrousTask(JoinableTask taskHasPendingMessages, int newPendingMessagesCount)
{
using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply())
Requires.NotNull(taskHasPendingMessages, nameof(taskHasPendingMessages));
Requires.Range(newPendingMessagesCount > 0, nameof(newPendingMessagesCount));
Assumes.True(Monitor.IsEntered(this.JoinableTaskContext.SyncContextLock));
Assumes.True((this.state & JoinableTaskFlags.CompletingSynchronously) == JoinableTaskFlags.CompletingSynchronously);
if (this.pendingEventSource == null || taskHasPendingMessages == this)
{
lock (this.owner.Context.SyncContextLock)
this.pendingEventSource = new WeakReference<JoinableTask>(taskHasPendingMessages);
}
this.pendingEventCount += newPendingMessagesCount;
return this.queueNeedProcessEvent;
}
private bool TryDequeueSelfOrDependencies(bool onMainThread, ref HashSet<IJoinableTaskDependent> visited, out SingleExecuteProtector work, out Task tryAgainAfter)
{
using (this.JoinableTaskContext.NoMessagePumpSynchronizationContext.Apply())
{
lock (this.JoinableTaskContext.SyncContextLock)
{
if (this.IsCompleted)
{
@ -979,7 +1025,7 @@ namespace Microsoft.VisualStudio.Threading
if (this.pendingEventSource != null)
{
if (this.pendingEventSource.TryGetTarget(out JoinableTask pendingSource) && pendingSource.IsDependingSynchronousTask(this))
if (this.pendingEventSource.TryGetTarget(out JoinableTask pendingSource) && JoinableTaskDependencyGraph.IsDependingSynchronousTask(pendingSource, this))
{
var queue = onMainThread ? pendingSource.mainThreadQueue : pendingSource.threadPoolQueue;
if (queue != null && !queue.IsCompleted && queue.TryDequeue(out work))
@ -999,14 +1045,14 @@ namespace Microsoft.VisualStudio.Threading
if (visited == null)
{
visited = new HashSet<JoinableTask>();
visited = new HashSet<IJoinableTaskDependent>();
}
else
{
visited.Clear();
}
if (this.TryDequeueSelfOrDependencies(onMainThread, visited, out work))
if (TryDequeueSelfOrDependencies(this, onMainThread, visited, out work))
{
tryAgainAfter = null;
return true;
@ -1022,28 +1068,33 @@ namespace Microsoft.VisualStudio.Threading
}
}
private bool TryDequeueSelfOrDependencies(bool onMainThread, HashSet<JoinableTask> visited, out SingleExecuteProtector work)
private static bool TryDequeueSelfOrDependencies(IJoinableTaskDependent currentNode, bool onMainThread, HashSet<IJoinableTaskDependent> visited, out SingleExecuteProtector work)
{
Requires.NotNull(currentNode, nameof(currentNode));
Requires.NotNull(visited, nameof(visited));
Report.IfNot(Monitor.IsEntered(this.owner.Context.SyncContextLock));
Report.IfNot(Monitor.IsEntered(currentNode.JoinableTaskContext.SyncContextLock));
// We only need to find the first work item.
work = null;
if (visited.Add(this))
if (visited.Add(currentNode))
{
var queue = onMainThread ? this.mainThreadQueue : this.threadPoolQueue;
if (queue != null && !queue.IsCompleted)
JoinableTask joinableTask = currentNode as JoinableTask;
if (joinableTask != null)
{
queue.TryDequeue(out work);
var queue = onMainThread ? joinableTask.mainThreadQueue : joinableTask.threadPoolQueue;
if (queue != null && !queue.IsCompleted)
{
queue.TryDequeue(out work);
}
}
if (work == null)
{
if (this.childOrJoinedJobs != null && !this.IsCompleted)
if (joinableTask?.IsCompleted != true)
{
foreach (var item in this.childOrJoinedJobs)
foreach (var item in JoinableTaskDependencyGraph.GetDirectDependentNodes(currentNode))
{
if (item.Key.TryDequeueSelfOrDependencies(onMainThread, visited, out work))
if (TryDequeueSelfOrDependencies(item, onMainThread, visited, out work))
{
break;
}
@ -1056,66 +1107,20 @@ namespace Microsoft.VisualStudio.Threading
}
/// <summary>
/// Adds a <see cref="JoinableTask"/> instance as one that is relevant to the async operation.
/// Adds the specified flags to the <see cref="state"/> field.
/// </summary>
/// <param name="joinChild">The <see cref="JoinableTask"/> to join as a child.</param>
internal JoinRelease AddDependency(JoinableTask joinChild)
private void AddStateFlags(JoinableTaskFlags flags)
{
Requires.NotNull(joinChild, nameof(joinChild));
if (this == joinChild)
// Try to avoid taking a lock if the flags are already set appropriately.
if ((this.state & flags) != flags)
{
// Joining oneself would be pointless.
return default(JoinRelease);
}
using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply())
{
List<AsyncManualResetEvent> eventsNeedNotify = null;
lock (this.owner.Context.SyncContextLock)
using (this.JoinableTaskContext.NoMessagePumpSynchronizationContext.Apply())
{
if (this.childOrJoinedJobs == null)
lock (this.JoinableTaskContext.SyncContextLock)
{
this.childOrJoinedJobs = new WeakKeyDictionary<JoinableTask, int>(capacity: 3);
}
this.childOrJoinedJobs.TryGetValue(joinChild, out int refCount);
this.childOrJoinedJobs[joinChild] = ++refCount;
if (refCount == 1)
{
// This constitutes a significant change, so we should apply synchronous task tracking to the new child.
var tasksNeedNotify = this.AddDependingSynchronousTaskToChild(joinChild);
if (tasksNeedNotify.Count > 0)
{
eventsNeedNotify = new List<AsyncManualResetEvent>(tasksNeedNotify.Count);
foreach (var taskToNotify in tasksNeedNotify)
{
if (taskToNotify.SynchronousTask.pendingEventSource == null || taskToNotify.TaskHasPendingMessages == taskToNotify.SynchronousTask)
{
taskToNotify.SynchronousTask.pendingEventSource = new WeakReference<JoinableTask>(taskToNotify.TaskHasPendingMessages);
}
taskToNotify.SynchronousTask.pendingEventCount += taskToNotify.NewPendingMessagesCount;
var notifyEvent = taskToNotify.SynchronousTask.queueNeedProcessEvent;
if (notifyEvent != null)
{
eventsNeedNotify.Add(notifyEvent);
}
}
}
this.state |= flags;
}
}
// We explicitly do this outside our lock.
if (eventsNeedNotify != null)
{
foreach (var queueEvent in eventsNeedNotify)
{
queueEvent.PulseAll();
}
}
return new JoinRelease(this, joinChild);
}
}
@ -1123,10 +1128,10 @@ namespace Microsoft.VisualStudio.Threading
{
if (!this.IsCompleted)
{
var ambientJob = this.owner.Context.AmbientTask;
var ambientJob = this.JoinableTaskContext.AmbientTask;
if (ambientJob != null && ambientJob != this)
{
return ambientJob.AddDependency(this);
return JoinableTaskDependencyGraph.AddDependency(ambientJob, this);
}
}

Просмотреть файл

@ -9,8 +9,6 @@ namespace Microsoft.VisualStudio.Threading
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -18,23 +16,8 @@ namespace Microsoft.VisualStudio.Threading
/// A collection of joinable tasks.
/// </summary>
[DebuggerDisplay("JoinableTaskCollection: {displayName ?? \"(anonymous)\"}")]
public class JoinableTaskCollection : IEnumerable<JoinableTask>
public class JoinableTaskCollection : IJoinableTaskDependent, IEnumerable<JoinableTask>
{
/// <summary>
/// The set of joinable tasks that belong to this collection -- that is, the set of joinable tasks that are implicitly Joined
/// when folks Join this collection.
/// The value is the number of times the joinable was added to this collection (and not yet removed)
/// if this collection is ref counted; otherwise the value is always 1.
/// </summary>
private readonly WeakKeyDictionary<JoinableTask, int> joinables = new WeakKeyDictionary<JoinableTask, int>(capacity: 2);
/// <summary>
/// The set of joinable tasks that have Joined this collection -- that is, the set of joinable tasks that are interested
/// in the completion of any and all joinable tasks that belong to this collection.
/// The value is the number of times a particular joinable task has Joined this collection.
/// </summary>
private readonly WeakKeyDictionary<JoinableTask, int> joiners = new WeakKeyDictionary<JoinableTask, int>(capacity: 2);
/// <summary>
/// A value indicating whether joinable tasks are only removed when completed or removed as many times as they were added.
/// </summary>
@ -46,6 +29,12 @@ namespace Microsoft.VisualStudio.Threading
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string displayName;
/// <summary>
/// The <see cref="JoinableTaskDependencyGraph.JoinableTaskDependentData"/> to track dependencies between tasks.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private JoinableTaskDependencyGraph.JoinableTaskDependentData dependentData;
/// <summary>
/// An event that is set when the collection is empty. (lazily initialized)
/// </summary>
@ -70,7 +59,7 @@ namespace Microsoft.VisualStudio.Threading
/// <summary>
/// Gets the <see cref="JoinableTaskContext"/> to which this collection belongs.
/// </summary>
public JoinableTaskContext Context { get; private set; }
public JoinableTaskContext Context { get; }
/// <summary>
/// Gets or sets a human-readable name that may appear in hang reports.
@ -86,6 +75,18 @@ namespace Microsoft.VisualStudio.Threading
set { this.displayName = value; }
}
/// <summary>
/// Gets JoinableTaskContext for <see cref="JoinableTaskDependencyGraph.JoinableTaskDependentData"/> to access locks.
/// </summary>
JoinableTaskContext IJoinableTaskDependent.JoinableTaskContext => this.Context;
/// <summary>
/// Gets a value indicating whether we need count reference for child dependent nodes.
/// </summary>
bool IJoinableTaskDependent.NeedRefCountChildDependencies => this.refCountAddedJobs;
ref JoinableTaskDependencyGraph.JoinableTaskDependentData IJoinableTaskDependent.GetJoinableTaskDependentData() => ref this.dependentData;
/// <summary>
/// Adds the specified joinable task to this collection.
/// </summary>
@ -98,37 +99,7 @@ namespace Microsoft.VisualStudio.Threading
Requires.Argument(false, "joinableTask", Strings.JoinableTaskContextAndCollectionMismatch);
}
if (!joinableTask.IsCompleted)
{
using (this.Context.NoMessagePumpSynchronizationContext.Apply())
{
lock (this.Context.SyncContextLock)
{
if (!this.joinables.TryGetValue(joinableTask, out int refCount) || this.refCountAddedJobs)
{
this.joinables[joinableTask] = refCount + 1;
if (refCount == 0)
{
joinableTask.OnAddedToCollection(this);
// Now that we've added a joinable task to our collection, any folks who
// have already joined this collection should be joined to this joinable task.
foreach (var joiner in this.joiners)
{
// We can discard the JoinRelease result of AddDependency
// because we directly disjoin without that helper struct.
joiner.Key.AddDependency(joinableTask);
}
}
}
if (this.emptyEvent != null)
{
this.emptyEvent.Reset();
}
}
}
}
JoinableTaskDependencyGraph.AddDependency(this, joinableTask);
}
/// <summary>
@ -139,40 +110,7 @@ namespace Microsoft.VisualStudio.Threading
public void Remove(JoinableTask joinableTask)
{
Requires.NotNull(joinableTask, nameof(joinableTask));
using (this.Context.NoMessagePumpSynchronizationContext.Apply())
{
lock (this.Context.SyncContextLock)
{
if (this.joinables.TryGetValue(joinableTask, out int refCount))
{
if (refCount == 1 || joinableTask.IsCompleted)
{ // remove regardless of ref count if job is completed
this.joinables.Remove(joinableTask);
joinableTask.OnRemovedFromCollection(this);
// Now that we've removed a joinable task from our collection, any folks who
// have already joined this collection should be disjoined to this joinable task
// as an efficiency improvement so we don't grow our weak collections unnecessarily.
foreach (var joiner in this.joiners)
{
// We can discard the JoinRelease result of AddDependency
// because we directly disjoin without that helper struct.
joiner.Key.RemoveDependency(joinableTask);
}
if (this.emptyEvent != null && this.joinables.Count == 0)
{
this.emptyEvent.Set();
}
}
else
{
this.joinables[joinableTask] = refCount - 1;
}
}
}
}
JoinableTaskDependencyGraph.RemoveDependency(this, joinableTask);
}
/// <summary>
@ -192,33 +130,24 @@ namespace Microsoft.VisualStudio.Threading
return default(JoinRelease);
}
using (this.Context.NoMessagePumpSynchronizationContext.Apply())
{
lock (this.Context.SyncContextLock)
{
this.joiners.TryGetValue(ambientJob, out int count);
this.joiners[ambientJob] = count + 1;
if (count == 0)
{
// The joining job was not previously joined to this collection,
// so we need to join each individual job within the collection now.
foreach (var joinable in this.joinables)
{
ambientJob.AddDependency(joinable.Key);
}
}
return new JoinRelease(this, ambientJob);
}
}
return JoinableTaskDependencyGraph.AddDependency(ambientJob, this);
}
/// <summary>
/// Joins the caller's context to this collection till the collection is empty.
/// </summary>
/// <returns>A task that completes when this collection is empty.</returns>
public async Task JoinTillEmptyAsync()
public Task JoinTillEmptyAsync() => this.JoinTillEmptyAsync(CancellationToken.None);
/// <summary>
/// Joins the caller's context to this collection till the collection is empty.
/// </summary>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>A task that completes when this collection is empty, or is canceled when <paramref name="cancellationToken"/> is canceled.</returns>
public async Task JoinTillEmptyAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (this.emptyEvent == null)
{
// We need a read lock to protect against the emptiness of this collection changing
@ -229,14 +158,14 @@ namespace Microsoft.VisualStudio.Threading
{
// We use interlocked here to mitigate race conditions in lazily initializing this field.
// We *could* take a write lock above, but that would needlessly increase lock contention.
var nowait = Interlocked.CompareExchange(ref this.emptyEvent, new AsyncManualResetEvent(this.joinables.Count == 0), null);
var nowait = Interlocked.CompareExchange(ref this.emptyEvent, new AsyncManualResetEvent(JoinableTaskDependencyGraph.HasNoChildDependentNode(this)), null);
}
}
}
using (this.Join())
{
await this.emptyEvent.WaitAsync().ConfigureAwait(false);
await this.emptyEvent.WaitAsync().WithCancellation(cancellationToken).ConfigureAwait(false);
}
}
@ -251,7 +180,7 @@ namespace Microsoft.VisualStudio.Threading
{
lock (this.Context.SyncContextLock)
{
return this.joinables.ContainsKey(joinableTask);
return JoinableTaskDependencyGraph.HasDirectDependency(this, joinableTask);
}
}
}
@ -266,9 +195,12 @@ namespace Microsoft.VisualStudio.Threading
var joinables = new List<JoinableTask>();
lock (this.Context.SyncContextLock)
{
foreach (var item in this.joinables)
foreach (var item in JoinableTaskDependencyGraph.GetDirectDependentNodes(this))
{
joinables.Add(item.Key);
if (item is JoinableTask joinableTask)
{
joinables.Add(joinableTask);
}
}
}
@ -284,34 +216,27 @@ namespace Microsoft.VisualStudio.Threading
return this.GetEnumerator();
}
/// <summary>
/// Breaks a join formed between the specified joinable task and this collection.
/// </summary>
/// <param name="joinableTask">The joinable task that had previously joined this collection, and that now intends to revert it.</param>
internal void Disjoin(JoinableTask joinableTask)
void IJoinableTaskDependent.OnAddedToDependency(IJoinableTaskDependent parent)
{
Requires.NotNull(joinableTask, nameof(joinableTask));
}
using (this.Context.NoMessagePumpSynchronizationContext.Apply())
void IJoinableTaskDependent.OnRemovedFromDependency(IJoinableTaskDependent parentNode)
{
}
void IJoinableTaskDependent.OnDependencyAdded(IJoinableTaskDependent joinChild)
{
if (this.emptyEvent != null && joinChild is JoinableTask)
{
lock (this.Context.SyncContextLock)
{
this.joiners.TryGetValue(joinableTask, out int count);
if (count == 1)
{
this.joiners.Remove(joinableTask);
this.emptyEvent.Reset();
}
}
// We also need to disjoin this joinable task from all joinable tasks in this collection.
foreach (var joinable in this.joinables)
{
joinableTask.RemoveDependency(joinable.Key);
}
}
else
{
this.joiners[joinableTask] = count - 1;
}
}
void IJoinableTaskDependent.OnDependencyRemoved(IJoinableTaskDependent joinChild)
{
if (this.emptyEvent != null && JoinableTaskDependencyGraph.HasNoChildDependentNode(this))
{
this.emptyEvent.Set();
}
}
@ -321,38 +246,21 @@ namespace Microsoft.VisualStudio.Threading
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
public struct JoinRelease : IDisposable
{
private JoinableTask joinedJob;
private JoinableTask joiner;
private JoinableTaskCollection joinedJobCollection;
private IJoinableTaskDependent parentDependencyNode;
private IJoinableTaskDependent childDependencyNode;
/// <summary>
/// Initializes a new instance of the <see cref="JoinRelease"/> struct.
/// </summary>
/// <param name="joined">The Main thread controlling SingleThreadSynchronizationContext to use to accelerate execution of Main thread bound work.</param>
/// <param name="joiner">The instance that created this value.</param>
internal JoinRelease(JoinableTask joined, JoinableTask joiner)
/// <param name="parentDependencyNode">The Main thread controlling SingleThreadSynchronizationContext to use to accelerate execution of Main thread bound work.</param>
/// <param name="childDependencyNode">The instance that created this value.</param>
internal JoinRelease(IJoinableTaskDependent parentDependencyNode, IJoinableTaskDependent childDependencyNode)
{
Requires.NotNull(joined, nameof(joined));
Requires.NotNull(joiner, nameof(joiner));
Requires.NotNull(parentDependencyNode, nameof(parentDependencyNode));
Requires.NotNull(childDependencyNode, nameof(childDependencyNode));
this.joinedJobCollection = null;
this.joinedJob = joined;
this.joiner = joiner;
}
/// <summary>
/// Initializes a new instance of the <see cref="JoinRelease"/> struct.
/// </summary>
/// <param name="jobCollection">The collection of joinable tasks that has been joined.</param>
/// <param name="joiner">The instance that created this value.</param>
internal JoinRelease(JoinableTaskCollection jobCollection, JoinableTask joiner)
{
Requires.NotNull(jobCollection, nameof(jobCollection));
Requires.NotNull(joiner, nameof(joiner));
this.joinedJobCollection = jobCollection;
this.joinedJob = null;
this.joiner = joiner;
this.parentDependencyNode = parentDependencyNode;
this.childDependencyNode = childDependencyNode;
}
/// <summary>
@ -360,19 +268,13 @@ namespace Microsoft.VisualStudio.Threading
/// </summary>
public void Dispose()
{
if (this.joinedJob != null)
if (this.parentDependencyNode != null)
{
this.joinedJob.RemoveDependency(this.joiner);
this.joinedJob = null;
JoinableTaskDependencyGraph.RemoveDependency(this.parentDependencyNode, this.childDependencyNode);
this.parentDependencyNode = null;
}
if (this.joinedJobCollection != null)
{
this.joinedJobCollection.Disjoin(this.joiner);
this.joinedJobCollection = null;
}
this.joiner = null;
this.childDependencyNode = null;
}
}
}

Просмотреть файл

@ -71,7 +71,7 @@ namespace Microsoft.VisualStudio.Threading
var links = new List<XElement>();
foreach (var joinableTaskAndElement in pendingTasksElements)
{
foreach (var joinedTask in joinableTaskAndElement.Key.ChildOrJoinedJobs)
foreach (var joinedTask in JoinableTaskDependencyGraph.GetAllDirectlyDependentJoinableTasks(joinableTaskAndElement.Key))
{
if (pendingTasksElements.TryGetValue(joinedTask, out XElement joinedTaskElement))
{

Просмотреть файл

@ -86,7 +86,7 @@ namespace Microsoft.VisualStudio.Threading
/// <summary>
/// An AsyncLocal value that carries the joinable instance associated with an async operation.
/// </summary>
private readonly AsyncLocal<JoinableTask> joinableOperation = new AsyncLocal<JoinableTask>();
private readonly AsyncLocal<WeakReference<JoinableTask>> joinableOperation = new AsyncLocal<WeakReference<JoinableTask>>();
/// <summary>
/// The set of tasks that have started but have not yet completed.
@ -192,18 +192,21 @@ namespace Microsoft.VisualStudio.Threading
{
get
{
using (this.NoMessagePumpSynchronizationContext.Apply())
if (this.nonJoinableFactory == null)
{
lock (this.SyncContextLock)
using (this.NoMessagePumpSynchronizationContext.Apply())
{
if (this.nonJoinableFactory == null)
lock (this.SyncContextLock)
{
this.nonJoinableFactory = this.CreateDefaultFactory();
if (this.nonJoinableFactory == null)
{
this.nonJoinableFactory = this.CreateDefaultFactory();
}
}
return this.nonJoinableFactory;
}
}
return this.nonJoinableFactory;
}
}
@ -251,8 +254,14 @@ namespace Microsoft.VisualStudio.Threading
/// </summary>
internal JoinableTask AmbientTask
{
get { return this.joinableOperation.Value; }
set { this.joinableOperation.Value = value; }
get
{
JoinableTask result = null;
this.joinableOperation.Value?.TryGetTarget(out result);
return result;
}
set => this.joinableOperation.Value = value?.WeakSelf;
}
/// <summary>
@ -323,7 +332,7 @@ namespace Microsoft.VisualStudio.Threading
var ambientTask = this.AmbientTask;
if (ambientTask != null)
{
if (ambientTask.HasMainThreadSynchronousTaskWaiting)
if (JoinableTaskDependencyGraph.HasMainThreadSynchronousTaskWaiting(ambientTask))
{
return true;
}
@ -337,24 +346,27 @@ namespace Microsoft.VisualStudio.Threading
{
lock (this.SyncContextLock)
{
var allJoinedJobs = new HashSet<JoinableTask>();
lock (this.initializingSynchronouslyMainThreadTasks)
{
// our read lock doesn't cover this collection
foreach (var initializingTask in this.initializingSynchronouslyMainThreadTasks)
if (this.initializingSynchronouslyMainThreadTasks.Count > 0)
{
if (!initializingTask.HasMainThreadSynchronousTaskWaiting)
// our read lock doesn't cover this collection
var allJoinedJobs = new HashSet<JoinableTask>();
foreach (var initializingTask in this.initializingSynchronouslyMainThreadTasks)
{
// This task blocks the main thread. If it has joined the ambient task
// directly or indirectly, then our ambient task is considered blocking
// the main thread.
initializingTask.AddSelfAndDescendentOrJoinedJobs(allJoinedJobs);
if (allJoinedJobs.Contains(ambientTask))
if (!JoinableTaskDependencyGraph.HasMainThreadSynchronousTaskWaiting(initializingTask))
{
return true;
}
// This task blocks the main thread. If it has joined the ambient task
// directly or indirectly, then our ambient task is considered blocking
// the main thread.
JoinableTaskDependencyGraph.AddSelfAndDescendentOrJoinedJobs(initializingTask, allJoinedJobs);
if (allJoinedJobs.Contains(ambientTask))
{
return true;
}
allJoinedJobs.Clear();
allJoinedJobs.Clear();
}
}
}
}

Просмотреть файл

@ -0,0 +1,917 @@
/********************************************************
* *
* © Copyright (C) Microsoft. All rights reserved. *
* *
*********************************************************/
namespace Microsoft.VisualStudio.Threading
{
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
/// <summary>
/// Methods to maintain dependencies between <see cref="JoinableTask"/>.
/// Those methods are expected to be called by <see cref="JoinableTask"/> or <see cref="JoinableTaskCollection"/> only to maintain relationship between them, and should not be called directly by other code.
/// </summary>
internal static class JoinableTaskDependencyGraph
{
/// <summary>
/// Gets a value indicating whether there is no child depenent item.
/// This method is expected to be used with the JTF lock.
/// </summary>
internal static bool HasNoChildDependentNode(IJoinableTaskDependent taskItem)
{
Requires.NotNull(taskItem, nameof(taskItem));
Assumes.True(Monitor.IsEntered(taskItem.JoinableTaskContext.SyncContextLock));
return taskItem.GetJoinableTaskDependentData().HasNoChildDependentNode;
}
/// <summary>
/// Checks whether a task or collection is a directly dependent of this item.
/// This method is expected to be used with the JTF lock.
/// </summary>
internal static bool HasDirectDependency(IJoinableTaskDependent taskItem, IJoinableTaskDependent dependency)
{
Requires.NotNull(taskItem, nameof(taskItem));
Assumes.True(Monitor.IsEntered(taskItem.JoinableTaskContext.SyncContextLock));
return taskItem.GetJoinableTaskDependentData().HasDirectDependency(dependency);
}
/// <summary>
/// Gets a value indicating whether the main thread is waiting for the task's completion
/// </summary>
internal static bool HasMainThreadSynchronousTaskWaiting(IJoinableTaskDependent taskItem)
{
Requires.NotNull(taskItem, nameof(taskItem));
using (taskItem.JoinableTaskContext.NoMessagePumpSynchronizationContext.Apply())
{
lock (taskItem.JoinableTaskContext.SyncContextLock)
{
return taskItem.GetJoinableTaskDependentData().HasMainThreadSynchronousTaskWaiting();
}
}
}
/// <summary>
/// Adds a <see cref="JoinableTaskDependentData"/> instance as one that is relevant to the async operation.
/// </summary>
/// <param name="taskItem">The current joinableTask or collection.</param>
/// <param name="joinChild">The <see cref="IJoinableTaskDependent"/> to join as a child.</param>
internal static JoinableTaskCollection.JoinRelease AddDependency(IJoinableTaskDependent taskItem, IJoinableTaskDependent joinChild)
{
Requires.NotNull(taskItem, nameof(taskItem));
return JoinableTaskDependentData.AddDependency(taskItem, joinChild);
}
/// <summary>
/// Removes a <see cref="IJoinableTaskDependent"/> instance as one that is no longer relevant to the async operation.
/// </summary>
/// <param name="taskItem">The current joinableTask or collection.</param>
/// <param name="child">The <see cref="IJoinableTaskDependent"/> to join as a child.</param>
internal static void RemoveDependency(IJoinableTaskDependent taskItem, IJoinableTaskDependent child)
{
Requires.NotNull(taskItem, nameof(taskItem));
JoinableTaskDependentData.RemoveDependency(taskItem, child);
}
/// <summary>
/// Gets all dependent nodes registered in the dependency collection.
/// This method is expected to be used with the JTF lock.
/// </summary>
internal static IEnumerable<IJoinableTaskDependent> GetDirectDependentNodes(IJoinableTaskDependent taskItem)
{
Requires.NotNull(taskItem, nameof(taskItem));
Assumes.True(Monitor.IsEntered(taskItem.JoinableTaskContext.SyncContextLock));
return taskItem.GetJoinableTaskDependentData().GetDirectDependentNodes();
}
/// <summary>
/// Check whether a task is being tracked in our tracking list.
/// </summary>
internal static bool IsDependingSynchronousTask(IJoinableTaskDependent taskItem, JoinableTask syncTask)
{
Requires.NotNull(taskItem, nameof(taskItem));
return taskItem.GetJoinableTaskDependentData().IsDependingSynchronousTask(syncTask);
}
/// <summary>
/// Calculate the collection of events we need trigger after we enqueue a request.
/// This method is expected to be used with the JTF lock.
/// </summary>
/// <param name="taskItem">The current joinableTask or collection.</param>
/// <param name="forMainThread">True if we want to find tasks to process the main thread queue. Otherwise tasks to process the background queue.</param>
/// <returns>The collection of synchronous tasks we need notify.</returns>
internal static IReadOnlyCollection<JoinableTask> GetDependingSynchronousTasks(IJoinableTaskDependent taskItem, bool forMainThread)
{
Requires.NotNull(taskItem, nameof(taskItem));
Assumes.True(Monitor.IsEntered(taskItem.JoinableTaskContext.SyncContextLock));
return taskItem.GetJoinableTaskDependentData().GetDependingSynchronousTasks(forMainThread);
}
/// <summary>
/// Gets a snapshot of all joined tasks.
/// FOR DIAGNOSTICS COLLECTION ONLY.
/// This method is expected to be used with the JTF lock.
/// </summary>
internal static IEnumerable<JoinableTask> GetAllDirectlyDependentJoinableTasks(IJoinableTaskDependent taskItem)
{
Requires.NotNull(taskItem, nameof(taskItem));
return JoinableTaskDependentData.GetAllDirectlyDependentJoinableTasks(taskItem);
}
/// <summary>
/// Recursively adds this joinable and all its dependencies to the specified set, that are not yet completed.
/// </summary>
internal static void AddSelfAndDescendentOrJoinedJobs(IJoinableTaskDependent taskItem, HashSet<JoinableTask> joinables)
{
Requires.NotNull(taskItem, nameof(taskItem));
JoinableTaskDependentData.AddSelfAndDescendentOrJoinedJobs(taskItem, joinables);
}
/// <summary>
/// When the current dependent node is a synchronous task, this method is called before the thread is blocked to wait it to complete.
/// This adds the current task to the dependingSynchronousTaskTracking list of the task itself (which will propergate through its dependencies.)
/// After the task is finished, <see cref="OnSynchronousTaskEndToBlockWaiting"/> is called to revert this change.
/// This method is expected to be used with the JTF lock.
/// </summary>
/// <param name="taskItem">The current joinableTask or collection.</param>
/// <param name="taskHasPendingRequests">Return the JoinableTask which has already had pending requests to be handled.</param>
/// <param name="pendingRequestsCount">The number of pending requests.</param>
internal static void OnSynchronousTaskStartToBlockWaiting(JoinableTask taskItem, out JoinableTask taskHasPendingRequests, out int pendingRequestsCount)
{
Requires.NotNull(taskItem, nameof(taskItem));
Assumes.True(Monitor.IsEntered(taskItem.Factory.Context.SyncContextLock));
JoinableTaskDependentData.OnSynchronousTaskStartToBlockWaiting(taskItem, out taskHasPendingRequests, out pendingRequestsCount);
}
/// <summary>
/// When the current dependent node is a synchronous task, this method is called after the synchronous is completed, and the thread is no longer blocked.
/// This removes the current task from the dependingSynchronousTaskTracking list of the task itself (and propergate through its dependencies.)
/// It reverts the data structure change done in the <see cref="OnSynchronousTaskStartToBlockWaiting"/>.
/// </summary>
internal static void OnSynchronousTaskEndToBlockWaiting(JoinableTask taskItem)
{
Requires.NotNull(taskItem, nameof(taskItem));
JoinableTaskDependentData.OnSynchronousTaskEndToBlockWaiting(taskItem);
}
/// <summary>
/// Remove all synchronous tasks tracked by the this task.
/// This is called when this task is completed.
/// This method is expected to be used with the JTF lock.
/// </summary>
internal static void OnTaskCompleted(IJoinableTaskDependent taskItem)
{
Requires.NotNull(taskItem, nameof(taskItem));
Assumes.True(Monitor.IsEntered(taskItem.JoinableTaskContext.SyncContextLock));
taskItem.GetJoinableTaskDependentData().OnTaskCompleted();
}
/// <summary>
/// Preserve data for the JoinableTask dependency tree. It is holded inside either a <see cref="JoinableTask"/> or a <see cref="JoinableTaskCollection"/>.
/// Do not call methods/properties directly anywhere out of <see cref="JoinableTaskDependencyGraph"/>.
/// </summary>
internal struct JoinableTaskDependentData
{
/// <summary>
/// Shared empty collection to prevent extra allocations.
/// </summary>
private static readonly JoinableTask[] EmptyJoinableTaskArray = new JoinableTask[0];
/// <summary>
/// Shared empty collection to prevent extra allocations.
/// </summary>
private static readonly PendingNotification[] EmptyPendingNotificationArray = new PendingNotification[0];
/// <summary>
/// A map of jobs that we should be willing to dequeue from when we control the UI thread, and a ref count. Lazily constructed.
/// </summary>
/// <remarks>
/// When the value in an entry is decremented to 0, the entry is removed from the map.
/// </remarks>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private WeakKeyDictionary<IJoinableTaskDependent, int> childDependentNodes;
/// <summary>
/// The head of a singly linked list of records to track which task may process events of this task.
/// This list should contain only tasks which need be completed synchronously, and depends on this task.
/// </summary>
private DependentSynchronousTask dependingSynchronousTaskTracking;
/// <summary>
/// Gets a value indicating whether the <see cref="childDependentNodes"/> is empty.
/// </summary>
internal bool HasNoChildDependentNode => this.childDependentNodes == null || this.childDependentNodes.Count == 0 || !this.childDependentNodes.Any();
/// <summary>
/// Gets all dependent nodes registered in the <see cref="childDependentNodes"/>
/// This method is expected to be used with the JTF lock.
/// </summary>
internal IEnumerable<IJoinableTaskDependent> GetDirectDependentNodes()
{
if (this.childDependentNodes == null)
{
return Enumerable.Empty<IJoinableTaskDependent>();
}
return this.childDependentNodes.Keys;
}
/// <summary>
/// Checks whether a dependent node is inside <see cref="childDependentNodes"/>.
/// This method is expected to be used with the JTF lock.
/// </summary>
internal bool HasDirectDependency(IJoinableTaskDependent dependency)
{
if (this.childDependentNodes == null)
{
return false;
}
return this.childDependentNodes.ContainsKey(dependency);
}
/// <summary>
/// Gets a value indicating whether the main thread is waiting for the task's completion
/// This method is expected to be used with the JTF lock.
/// </summary>
internal bool HasMainThreadSynchronousTaskWaiting()
{
DependentSynchronousTask existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking != null)
{
if ((existingTaskTracking.SynchronousTask.State & JoinableTask.JoinableTaskFlags.SynchronouslyBlockingMainThread) == JoinableTask.JoinableTaskFlags.SynchronouslyBlockingMainThread)
{
return true;
}
existingTaskTracking = existingTaskTracking.Next;
}
return false;
}
/// <summary>
/// Gets a snapshot of all joined tasks.
/// FOR DIAGNOSTICS COLLECTION ONLY.
/// This method is expected to be used with the JTF lock.
/// </summary>
/// <param name="taskOrCollection">The current joinableTask or collection contains this data.</param>
internal static IEnumerable<JoinableTask> GetAllDirectlyDependentJoinableTasks(IJoinableTaskDependent taskOrCollection)
{
Requires.NotNull(taskOrCollection, nameof(taskOrCollection));
Assumes.True(Monitor.IsEntered(taskOrCollection.JoinableTaskContext.SyncContextLock));
if (taskOrCollection.GetJoinableTaskDependentData().childDependentNodes == null)
{
return Enumerable.Empty<JoinableTask>();
}
var allTasks = new HashSet<JoinableTask>();
AddSelfAndDescendentOrJoinedJobs(taskOrCollection, allTasks);
return allTasks;
}
/// <summary>
/// Remove all synchronous tasks tracked by the this task.
/// This is called when this task is completed.
/// This method is expected to be used with the JTF lock.
/// </summary>
internal void OnTaskCompleted()
{
if (this.dependingSynchronousTaskTracking != null)
{
DependentSynchronousTask existingTaskTracking = this.dependingSynchronousTaskTracking;
this.dependingSynchronousTaskTracking = null;
if (this.childDependentNodes != null)
{
var childrenTasks = new List<IJoinableTaskDependent>(this.childDependentNodes.Keys);
while (existingTaskTracking != null)
{
RemoveDependingSynchronousTaskFrom(childrenTasks, existingTaskTracking.SynchronousTask, false);
existingTaskTracking = existingTaskTracking.Next;
}
}
}
}
/// <summary>
/// Adds a <see cref="JoinableTaskDependentData"/> instance as one that is relevant to the async operation.
/// </summary>
/// <param name="parentTaskOrCollection">The current joinableTask or collection contains to add a dependency.</param>
/// <param name="joinChild">The <see cref="IJoinableTaskDependent"/> to join as a child.</param>
internal static JoinableTaskCollection.JoinRelease AddDependency(IJoinableTaskDependent parentTaskOrCollection, IJoinableTaskDependent joinChild)
{
Requires.NotNull(parentTaskOrCollection, nameof(parentTaskOrCollection));
Requires.NotNull(joinChild, nameof(joinChild));
if (parentTaskOrCollection == joinChild)
{
// Joining oneself would be pointless.
return default(JoinableTaskCollection.JoinRelease);
}
using (parentTaskOrCollection.JoinableTaskContext.NoMessagePumpSynchronizationContext.Apply())
{
List<AsyncManualResetEvent> eventsNeedNotify = null;
lock (parentTaskOrCollection.JoinableTaskContext.SyncContextLock)
{
var joinableTask = joinChild as JoinableTask;
if (joinableTask?.IsCompleted == true)
{
return default(JoinableTaskCollection.JoinRelease);
}
ref JoinableTaskDependentData data = ref parentTaskOrCollection.GetJoinableTaskDependentData();
if (data.childDependentNodes == null)
{
data.childDependentNodes = new WeakKeyDictionary<IJoinableTaskDependent, int>(capacity: 2);
}
if (data.childDependentNodes.TryGetValue(joinChild, out int refCount) && !parentTaskOrCollection.NeedRefCountChildDependencies)
{
return default(JoinableTaskCollection.JoinRelease);
}
data.childDependentNodes[joinChild] = ++refCount;
if (refCount == 1)
{
// This constitutes a significant change, so we should apply synchronous task tracking to the new child.
joinChild.OnAddedToDependency(parentTaskOrCollection);
var tasksNeedNotify = AddDependingSynchronousTaskToChild(parentTaskOrCollection, joinChild);
if (tasksNeedNotify.Count > 0)
{
eventsNeedNotify = new List<AsyncManualResetEvent>(tasksNeedNotify.Count);
foreach (var taskToNotify in tasksNeedNotify)
{
var notifyEvent = taskToNotify.SynchronousTask.RegisterPendingEventsForSynchrousTask(taskToNotify.TaskHasPendingMessages, taskToNotify.NewPendingMessagesCount);
if (notifyEvent != null)
{
eventsNeedNotify.Add(notifyEvent);
}
}
}
parentTaskOrCollection.OnDependencyAdded(joinChild);
}
}
// We explicitly do this outside our lock.
if (eventsNeedNotify != null)
{
foreach (var queueEvent in eventsNeedNotify)
{
queueEvent.PulseAll();
}
}
return new JoinableTaskCollection.JoinRelease(parentTaskOrCollection, joinChild);
}
}
/// <summary>
/// Removes a <see cref="IJoinableTaskDependent"/> instance as one that is no longer relevant to the async operation.
/// </summary>
/// <param name="parentTaskOrCollection">The current joinableTask or collection contains to remove a dependency.</param>
/// <param name="joinChild">The <see cref="IJoinableTaskDependent"/> to join as a child.</param>
internal static void RemoveDependency(IJoinableTaskDependent parentTaskOrCollection, IJoinableTaskDependent joinChild)
{
Requires.NotNull(parentTaskOrCollection, nameof(parentTaskOrCollection));
Requires.NotNull(joinChild, nameof(joinChild));
using (parentTaskOrCollection.JoinableTaskContext.NoMessagePumpSynchronizationContext.Apply())
{
ref JoinableTaskDependentData data = ref parentTaskOrCollection.GetJoinableTaskDependentData();
lock (parentTaskOrCollection.JoinableTaskContext.SyncContextLock)
{
if (data.childDependentNodes != null && data.childDependentNodes.TryGetValue(joinChild, out int refCount))
{
if (refCount == 1)
{
joinChild.OnRemovedFromDependency(parentTaskOrCollection);
data.childDependentNodes.Remove(joinChild);
data.RemoveDependingSynchronousTaskFromChild(joinChild);
parentTaskOrCollection.OnDependencyRemoved(joinChild);
}
else
{
data.childDependentNodes[joinChild] = --refCount;
}
}
}
}
}
/// <summary>
/// Recursively adds this joinable and all its dependencies to the specified set, that are not yet completed.
/// </summary>
/// <param name="taskOrCollection">The current joinableTask or collection contains this data.</param>
/// <param name="joinables">A collection to hold <see cref="JoinableTask"/> found.</param>
internal static void AddSelfAndDescendentOrJoinedJobs(IJoinableTaskDependent taskOrCollection, HashSet<JoinableTask> joinables)
{
Requires.NotNull(taskOrCollection, nameof(taskOrCollection));
Requires.NotNull(joinables, nameof(joinables));
if (taskOrCollection is JoinableTask thisJoinableTask)
{
if (thisJoinableTask.IsCompleted || !joinables.Add(thisJoinableTask))
{
return;
}
}
var childDependentNodes = taskOrCollection.GetJoinableTaskDependentData().childDependentNodes;
if (childDependentNodes != null)
{
foreach (var item in childDependentNodes)
{
AddSelfAndDescendentOrJoinedJobs(item.Key, joinables);
}
}
}
/// <summary>
/// Check whether a task is being tracked in our tracking list.
/// </summary>
internal bool IsDependingSynchronousTask(JoinableTask syncTask)
{
DependentSynchronousTask existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking != null)
{
if (existingTaskTracking.SynchronousTask == syncTask)
{
return true;
}
existingTaskTracking = existingTaskTracking.Next;
}
return false;
}
/// <summary>
/// Calculate the collection of events we need trigger after we enqueue a request.
/// This method is expected to be used with the JTF lock.
/// </summary>
/// <param name="forMainThread">True if we want to find tasks to process the main thread queue. Otherwise tasks to process the background queue.</param>
/// <returns>The collection of synchronous tasks we need notify.</returns>
internal IReadOnlyCollection<JoinableTask> GetDependingSynchronousTasks(bool forMainThread)
{
int count = this.CountOfDependingSynchronousTasks();
if (count == 0)
{
return EmptyJoinableTaskArray;
}
var tasksNeedNotify = new List<JoinableTask>(count);
DependentSynchronousTask existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking != null)
{
var syncTask = existingTaskTracking.SynchronousTask;
bool syncTaskInOnMainThread = (syncTask.State & JoinableTask.JoinableTaskFlags.SynchronouslyBlockingMainThread) == JoinableTask.JoinableTaskFlags.SynchronouslyBlockingMainThread;
if (forMainThread == syncTaskInOnMainThread)
{
// Only synchronous tasks are in the list, so we don't need do further check for the CompletingSynchronously flag
tasksNeedNotify.Add(syncTask);
}
existingTaskTracking = existingTaskTracking.Next;
}
return tasksNeedNotify;
}
/// <summary>
/// When the current dependent node is a synchronous task, this method is called before the thread is blocked to wait it to complete.
/// This adds the current task to the <see cref="dependingSynchronousTaskTracking"/> of the task itself (which will propergate through its dependencies.)
/// After the task is finished, <see cref="OnSynchronousTaskEndToBlockWaiting"/> is called to revert this change.
/// This method is expected to be used with the JTF lock.
/// </summary>
/// <param name="syncTask">The synchronized joinableTask.</param>
/// <param name="taskHasPendingRequests">Return the JoinableTask which has already had pending requests to be handled.</param>
/// <param name="pendingRequestsCount">The number of pending requests.</param>
internal static void OnSynchronousTaskStartToBlockWaiting(JoinableTask syncTask, out JoinableTask taskHasPendingRequests, out int pendingRequestsCount)
{
Requires.NotNull(syncTask, nameof(syncTask));
pendingRequestsCount = 0;
taskHasPendingRequests = null;
taskHasPendingRequests = AddDependingSynchronousTask(syncTask, syncTask, ref pendingRequestsCount);
}
/// <summary>
/// When the current dependent node is a synchronous task, this method is called after the synchronous is completed, and the thread is no longer blocked.
/// This removes the current task from the <see cref="dependingSynchronousTaskTracking"/> of the task itself (and propergate through its dependencies.)
/// It reverts the data structure change done in the <see cref="OnSynchronousTaskStartToBlockWaiting"/>.
/// </summary>
/// <param name="syncTask">The synchronized joinableTask.</param>
internal static void OnSynchronousTaskEndToBlockWaiting(JoinableTask syncTask)
{
Requires.NotNull(syncTask, nameof(syncTask));
using (syncTask.Factory.Context.NoMessagePumpSynchronizationContext.Apply())
{
lock (syncTask.Factory.Context.SyncContextLock)
{
// Remove itself from the tracking list, after the task is completed.
RemoveDependingSynchronousTask(syncTask, syncTask, true);
}
}
}
/// <summary>
/// Get how many number of synchronous tasks in our tracking list.
/// </summary>
private int CountOfDependingSynchronousTasks()
{
int count = 0;
DependentSynchronousTask existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking != null)
{
count++;
existingTaskTracking = existingTaskTracking.Next;
}
return count;
}
/// <summary>
/// Applies all synchronous tasks tracked by this task to a new child/dependent task.
/// </summary>
/// <param name="dependentNode">The current joinableTask or collection owns the data.</param>
/// <param name="child">The new child task.</param>
/// <returns>Pairs of synchronous tasks we need notify and the event source triggering it, plus the number of pending events.</returns>
private static IReadOnlyCollection<PendingNotification> AddDependingSynchronousTaskToChild(IJoinableTaskDependent dependentNode, IJoinableTaskDependent child)
{
Requires.NotNull(dependentNode, nameof(dependentNode));
Requires.NotNull(child, nameof(child));
Assumes.True(Monitor.IsEntered(dependentNode.JoinableTaskContext.SyncContextLock));
ref JoinableTaskDependentData data = ref dependentNode.GetJoinableTaskDependentData();
int count = data.CountOfDependingSynchronousTasks();
if (count == 0)
{
return EmptyPendingNotificationArray;
}
var tasksNeedNotify = new List<PendingNotification>(count);
DependentSynchronousTask existingTaskTracking = data.dependingSynchronousTaskTracking;
while (existingTaskTracking != null)
{
int totalEventNumber = 0;
var eventTriggeringTask = AddDependingSynchronousTask(child, existingTaskTracking.SynchronousTask, ref totalEventNumber);
if (eventTriggeringTask != null)
{
tasksNeedNotify.Add(new PendingNotification(existingTaskTracking.SynchronousTask, eventTriggeringTask, totalEventNumber));
}
existingTaskTracking = existingTaskTracking.Next;
}
return tasksNeedNotify;
}
/// <summary>
/// Removes all synchronous tasks we applies to a dependent task, after the relationship is removed.
/// </summary>
/// <param name="child">The original dependent task</param>
private void RemoveDependingSynchronousTaskFromChild(IJoinableTaskDependent child)
{
Requires.NotNull(child, nameof(child));
DependentSynchronousTask existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking != null)
{
RemoveDependingSynchronousTask(child, existingTaskTracking.SynchronousTask);
existingTaskTracking = existingTaskTracking.Next;
}
}
/// <summary>
/// Tracks a new synchronous task for this task.
/// A synchronous task is a task blocking a thread and waits it to be completed. We may want the blocking thread
/// to process events from this task.
/// </summary>
/// <param name="taskOrCollection">The current joinableTask or collection.</param>
/// <param name="synchronousTask">The synchronous task</param>
/// <param name="totalEventsPending">The total events need be processed</param>
/// <returns>The task causes us to trigger the event of the synchronous task, so it can process new events. Null means we don't need trigger any event</returns>
private static JoinableTask AddDependingSynchronousTask(IJoinableTaskDependent taskOrCollection, JoinableTask synchronousTask, ref int totalEventsPending)
{
Requires.NotNull(taskOrCollection, nameof(taskOrCollection));
Requires.NotNull(synchronousTask, nameof(synchronousTask));
Assumes.True(Monitor.IsEntered(taskOrCollection.JoinableTaskContext.SyncContextLock));
JoinableTask thisJoinableTask = taskOrCollection as JoinableTask;
if (thisJoinableTask != null)
{
if (thisJoinableTask.IsCompleted)
{
return null;
}
if (thisJoinableTask.IsCompleteRequested)
{
// A completed task might still have pending items in the queue.
int pendingCount = thisJoinableTask.GetPendingEventCountForSynchronousTask(synchronousTask);
if (pendingCount > 0)
{
totalEventsPending += pendingCount;
return thisJoinableTask;
}
return null;
}
}
ref JoinableTaskDependentData data = ref taskOrCollection.GetJoinableTaskDependentData();
DependentSynchronousTask existingTaskTracking = data.dependingSynchronousTaskTracking;
while (existingTaskTracking != null)
{
if (existingTaskTracking.SynchronousTask == synchronousTask)
{
existingTaskTracking.ReferenceCount++;
return null;
}
existingTaskTracking = existingTaskTracking.Next;
}
JoinableTask eventTriggeringTask = null;
if (thisJoinableTask != null)
{
int pendingItemCount = thisJoinableTask.GetPendingEventCountForSynchronousTask(synchronousTask);
if (pendingItemCount > 0)
{
totalEventsPending += pendingItemCount;
eventTriggeringTask = thisJoinableTask;
}
}
// For a new synchronous task, we need apply it to our child tasks.
DependentSynchronousTask newTaskTracking = new DependentSynchronousTask(synchronousTask)
{
Next = data.dependingSynchronousTaskTracking,
};
data.dependingSynchronousTaskTracking = newTaskTracking;
if (data.childDependentNodes != null)
{
foreach (var item in data.childDependentNodes)
{
var childTiggeringTask = AddDependingSynchronousTask(item.Key, synchronousTask, ref totalEventsPending);
if (eventTriggeringTask == null)
{
eventTriggeringTask = childTiggeringTask;
}
}
}
return eventTriggeringTask;
}
/// <summary>
/// Remove a synchronous task from the tracking list.
/// </summary>
/// <param name="taskOrCollection">The current joinableTask or collection.</param>
/// <param name="syncTask">The synchronous task</param>
/// <param name="force">We always remove it from the tracking list if it is true. Otherwise, we keep tracking the reference count.</param>
private static void RemoveDependingSynchronousTask(IJoinableTaskDependent taskOrCollection, JoinableTask syncTask, bool force = false)
{
Requires.NotNull(taskOrCollection, nameof(taskOrCollection));
Requires.NotNull(syncTask, nameof(syncTask));
Assumes.True(Monitor.IsEntered(taskOrCollection.JoinableTaskContext.SyncContextLock));
var syncTaskItem = (IJoinableTaskDependent)syncTask;
if (syncTaskItem.GetJoinableTaskDependentData().dependingSynchronousTaskTracking != null)
{
RemoveDependingSynchronousTaskFrom(new IJoinableTaskDependent[] { taskOrCollection }, syncTask, force);
}
}
/// <summary>
/// Remove a synchronous task from the tracking list of a list of tasks.
/// </summary>
/// <param name="tasks">A list of tasks we need update the tracking list.</param>
/// <param name="syncTask">The synchronous task we want to remove</param>
/// <param name="force">We always remove it from the tracking list if it is true. Otherwise, we keep tracking the reference count.</param>
private static void RemoveDependingSynchronousTaskFrom(IReadOnlyList<IJoinableTaskDependent> tasks, JoinableTask syncTask, bool force)
{
Requires.NotNull(tasks, nameof(tasks));
Requires.NotNull(syncTask, nameof(syncTask));
HashSet<IJoinableTaskDependent> reachableNodes = null;
HashSet<IJoinableTaskDependent> remainNodes = null;
if (force)
{
reachableNodes = new HashSet<IJoinableTaskDependent>();
}
foreach (var task in tasks)
{
RemoveDependingSynchronousTask(task, syncTask, reachableNodes, ref remainNodes);
}
if (!force && remainNodes != null && remainNodes.Count > 0)
{
// a set of tasks may form a dependent loop, so it will make the reference count system
// not to work correctly when we try to remove the synchronous task.
// To get rid of those loops, if a task still tracks the synchronous task after reducing
// the reference count, we will calculate the entire reachable tree from the root. That will
// tell us the exactly tasks which need track the synchronous task, and we will clean up the rest.
reachableNodes = new HashSet<IJoinableTaskDependent>();
var syncTaskItem = (IJoinableTaskDependent)syncTask;
ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks(syncTaskItem, reachableNodes, remainNodes);
// force to remove all invalid items
HashSet<IJoinableTaskDependent> remainPlaceHold = null;
foreach (var remainTask in remainNodes)
{
RemoveDependingSynchronousTask(remainTask, syncTask, reachableNodes, ref remainPlaceHold);
}
}
}
/// <summary>
/// Compute all reachable nodes from a synchronous task. Because we use the result to clean up invalid
/// items from the remain task, we will remove valid task from the collection, and stop immediately if nothing is left.
/// </summary>
/// <param name="taskOrCollection">The current joinableTask or collection owns the data.</param>
/// <param name="reachableNodes">All reachable dependency nodes. This is not a completed list, if there is no remain node.</param>
/// <param name="remainNodes">Remain dependency nodes we want to check. After the execution, it will retain non-reachable nodes.</param>
private static void ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks(IJoinableTaskDependent taskOrCollection, HashSet<IJoinableTaskDependent> reachableNodes, HashSet<IJoinableTaskDependent> remainNodes)
{
Requires.NotNull(taskOrCollection, nameof(taskOrCollection));
Requires.NotNull(remainNodes, nameof(remainNodes));
Requires.NotNull(reachableNodes, nameof(reachableNodes));
if ((taskOrCollection as JoinableTask)?.IsCompleted != true)
{
if (reachableNodes.Add(taskOrCollection))
{
if (remainNodes.Remove(taskOrCollection) && reachableNodes.Count == 0)
{
// no remain task left, quit the loop earlier
return;
}
var dependencies = taskOrCollection.GetJoinableTaskDependentData().childDependentNodes;
if (dependencies != null)
{
foreach (var item in dependencies)
{
ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks(item.Key, reachableNodes, remainNodes);
}
}
}
}
}
/// <summary>
/// Remove a synchronous task from the tracking list of this task.
/// </summary>
/// <param name="taskOrCollection">The current joinableTask or collection.</param>
/// <param name="task">The synchronous task</param>
/// <param name="reachableNodes">
/// If it is not null, it will contain all dependency nodes which can track the synchronous task. We will ignore reference count in that case.
/// </param>
/// <param name="remainingDependentNodes">This will retain the tasks which still tracks the synchronous task.</param>
private static void RemoveDependingSynchronousTask(IJoinableTaskDependent taskOrCollection, JoinableTask task, HashSet<IJoinableTaskDependent> reachableNodes, ref HashSet<IJoinableTaskDependent> remainingDependentNodes)
{
Requires.NotNull(taskOrCollection, nameof(taskOrCollection));
Requires.NotNull(task, nameof(task));
ref JoinableTaskDependentData data = ref taskOrCollection.GetJoinableTaskDependentData();
DependentSynchronousTask previousTaskTracking = null;
DependentSynchronousTask currentTaskTracking = data.dependingSynchronousTaskTracking;
bool removed = false;
while (currentTaskTracking != null)
{
if (currentTaskTracking.SynchronousTask == task)
{
if (--currentTaskTracking.ReferenceCount > 0)
{
if (reachableNodes != null)
{
if (!reachableNodes.Contains(taskOrCollection))
{
currentTaskTracking.ReferenceCount = 0;
}
}
}
if (currentTaskTracking.ReferenceCount == 0)
{
removed = true;
if (previousTaskTracking != null)
{
previousTaskTracking.Next = currentTaskTracking.Next;
}
else
{
data.dependingSynchronousTaskTracking = currentTaskTracking.Next;
}
}
if (reachableNodes == null)
{
if (removed)
{
if (remainingDependentNodes != null)
{
remainingDependentNodes.Remove(taskOrCollection);
}
}
else
{
if (remainingDependentNodes == null)
{
remainingDependentNodes = new HashSet<IJoinableTaskDependent>();
}
remainingDependentNodes.Add(taskOrCollection);
}
}
break;
}
previousTaskTracking = currentTaskTracking;
currentTaskTracking = currentTaskTracking.Next;
}
if (removed && data.childDependentNodes != null)
{
foreach (var item in data.childDependentNodes)
{
RemoveDependingSynchronousTask(item.Key, task, reachableNodes, ref remainingDependentNodes);
}
}
}
/// <summary>
/// The record of a pending notification we need send to the synchronous task that we have some new messages to process.
/// </summary>
private struct PendingNotification
{
internal PendingNotification(JoinableTask synchronousTask, JoinableTask taskHasPendingMessages, int newPendingMessagesCount)
{
Requires.NotNull(synchronousTask, nameof(synchronousTask));
Requires.NotNull(taskHasPendingMessages, nameof(taskHasPendingMessages));
this.SynchronousTask = synchronousTask;
this.TaskHasPendingMessages = taskHasPendingMessages;
this.NewPendingMessagesCount = newPendingMessagesCount;
}
/// <summary>
/// Gets the synchronous task which need process new messages.
/// </summary>
internal JoinableTask SynchronousTask { get; }
/// <summary>
/// Gets one JoinableTask which may have pending messages. We may have multiple new JoinableTasks which contains pending messages.
/// This is just one of them. It gives the synchronous task a way to start quickly without searching all messages.
/// </summary>
internal JoinableTask TaskHasPendingMessages { get; }
/// <summary>
/// Gets the total number of new pending messages. The real number could be less than that, but should not be more than that.
/// </summary>
internal int NewPendingMessagesCount { get; }
}
/// <summary>
/// A single linked list to maintain synchronous JoinableTask depends on the current task,
/// which may process the queue of the current task.
/// </summary>
private class DependentSynchronousTask
{
internal DependentSynchronousTask(JoinableTask task)
{
this.SynchronousTask = task;
this.ReferenceCount = 1;
}
/// <summary>
/// Gets or sets the chain of the single linked list
/// </summary>
internal DependentSynchronousTask Next { get; set; }
/// <summary>
/// Gets the synchronous task
/// </summary>
internal JoinableTask SynchronousTask { get; }
/// <summary>
/// Gets or sets the reference count. We remove the item from the list, if it reaches 0.
/// </summary>
internal int ReferenceCount { get; set; }
}
}
}
}

Просмотреть файл

@ -173,6 +173,39 @@ namespace Microsoft.VisualStudio.Threading
return new MainThreadAwaitable(this, this.Context.AmbientTask, cancellationToken);
}
/// <summary>
/// Gets an awaitable whose continuations execute on the synchronization context that this instance was initialized with,
/// in such a way as to mitigate both deadlocks and reentrancy.
/// </summary>
/// <param name="alwaysYield">A value indicating whether the caller should yield even if
/// already executing on the main thread.</param>
/// <param name="cancellationToken">
/// A token whose cancellation will immediately schedule the continuation
/// on a threadpool thread.
/// </param>
/// <returns>An awaitable.</returns>
/// <remarks>
/// <example>
/// <code>
/// private async Task SomeOperationAsync()
/// {
/// // This first part can be on the caller's thread, whatever that is.
/// DoSomething();
///
/// // Now switch to the Main thread to talk to some STA object.
/// // Supposing it is also important to *not* do this step on our caller's callstack,
/// // be sure we yield even if we're on the UI thread.
/// await this.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true);
/// STAService.DoSomething();
/// }
/// </code>
/// </example>
/// </remarks>
public MainThreadAwaitable SwitchToMainThreadAsync(bool alwaysYield, CancellationToken cancellationToken = default(CancellationToken))
{
return new MainThreadAwaitable(this, this.Context.AmbientTask, cancellationToken, alwaysYield);
}
/// <summary>
/// Responds to calls to <see cref="JoinableTaskFactory.MainThreadAwaiter.OnCompleted(Action)"/>
/// by scheduling a continuation to execute on the Main thread.
@ -377,7 +410,7 @@ namespace Microsoft.VisualStudio.Threading
var allJoinedJobs = new HashSet<JoinableTask>();
lock (this.Context.SyncContextLock)
{
currentBlockingTask.AddSelfAndDescendentOrJoinedJobs(allJoinedJobs);
JoinableTaskDependencyGraph.AddSelfAndDescendentOrJoinedJobs(currentBlockingTask, allJoinedJobs);
return allJoinedJobs.Any(t => (t.CreationOptions & JoinableTaskCreationOptions.LongRunning) == JoinableTaskCreationOptions.LongRunning);
}
}
@ -973,7 +1006,7 @@ namespace Microsoft.VisualStudio.Threading
// Join the ambient parent job, so the parent can dequeue this job's work.
if (this.previousJoinable != null && !this.previousJoinable.IsCompleted)
{
this.previousJoinable.AddDependency(joinable);
JoinableTaskDependencyGraph.AddDependency(this.previousJoinable, joinable);
// By definition we inherit the nesting factories of our immediate nesting task.
var nestingFactories = this.previousJoinable.NestingFactories;

Просмотреть файл

@ -42,10 +42,7 @@ namespace Microsoft.VisualStudio.Threading
/// <summary>
/// Gets the asynchronous task that completes when the async operation completes.
/// </summary>
public new Task<T> Task
{
get { return (Task<T>)base.Task; }
}
public new Task<T> Task => (Task<T>)base.Task;
/// <summary>
/// Joins any main thread affinity of the caller with the asynchronous operation to avoid deadlocks
@ -87,5 +84,14 @@ namespace Microsoft.VisualStudio.Threading
base.CompleteOnCurrentThread();
return this.Task.GetAwaiter().GetResult();
}
/// <inheritdoc/>
internal override object CreateTaskCompletionSource() => new TaskCompletionSourceWithoutInlining<T>(allowInliningContinuations: false);
/// <inheritdoc/>
internal override Task GetTaskFromCompletionSource(object taskCompletionSource) => ((TaskCompletionSourceWithoutInlining<T>)taskCompletionSource).Task;
/// <inheritdoc/>
internal override void CompleteTaskSourceFromWrappedTask(Task wrappedTask, object taskCompletionSource) => ((Task<T>)wrappedTask).ApplyResultTo((TaskCompletionSource<T>)taskCompletionSource);
}
}

Просмотреть файл

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.1;netstandard1.3;netstandard2.0;portable-net45+win8+wpa81;net45;net46</TargetFrameworks>
<TargetFrameworks>netstandard1.3;netstandard2.0;net45;net46</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<CodeAnalysisRuleSet>FxCopRules.ruleset</CodeAnalysisRuleSet>
@ -21,11 +21,8 @@
<PackageIconUrl>https://aka.ms/VsExtensibilityIcon</PackageIconUrl>
<PackageReleaseNotes>https://go.microsoft.com/fwlink/?LinkID=746387</PackageReleaseNotes>
<RunCodeAnalysis Condition=" '$(TargetFramework)' != 'portable-net45+win8+wpa81' and '$(TargetFramework)' != 'netstandard2.0' and '$(Configuration)' != 'Debug' ">true</RunCodeAnalysis>
<RunCodeAnalysis Condition=" '$(TargetFramework)' != 'netstandard2.0' and '$(Configuration)' != 'Debug' ">true</RunCodeAnalysis>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- Reference directly to use 4.3.1 and suppress package restore warnings. See https://github.com/dotnet/corefx/issues/29907 and https://github.com/AArnott/vs-threading/commit/357f279e44d150d2dbc305cf81fd549f2cbaed2d -->
<RuntimeIdentifiers Condition="'$(TargetFramework)' == 'portable-net45+win8+wpa81'">;</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Label="MultilingualAppToolkit">
<MultilingualAppToolkitVersion>4.0</MultilingualAppToolkitVersion>
@ -61,9 +58,9 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.3.0" />
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="15.3.15" />
<PackageReference Include="MicroBuild.VisualStudio" Version="$(MicroBuildVersion)" PrivateAssets="all" />
<PackageReference Include="MSBuild.SDK.Extras" Version="1.0.6" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<!-- Don't consume the analyzers in this library itself,
@ -71,7 +68,6 @@
<ProjectReference Include="..\Microsoft.VisualStudio.Threading.Analyzers\Microsoft.VisualStudio.Threading.Analyzers.csproj"
PrivateAssets="none" />
</ItemGroup>
<Import Project="$(MSBuildSDKExtrasTargets)" Condition="Exists('$(MSBuildSDKExtrasTargets)')" />
<Import Project="$(MSBuildExtensionsPath)\Microsoft\Multilingual App Toolkit\Microsoft.Multilingual.ResxResources.targets" Label="MultilingualAppToolkit" Condition="Exists('$(MSBuildExtensionsPath)\Microsoft\Multilingual App Toolkit\v$(MultilingualAppToolkitVersion)\Microsoft.Multilingual.ResxResources.targets')" />
<Target Name="MATPrerequisite" BeforeTargets="PrepareForBuild" Condition="!Exists('$(MSBuildExtensionsPath)\Microsoft\Multilingual App Toolkit\Microsoft.Multilingual.ResxResources.targets')" Label="MultilingualAppToolkit">
<Warning Text="$(MSBuildProjectFile) is Multilingual build enabled, but the Multilingual App Toolkit is unavailable during the build. If building with Visual Studio, please check to ensure that toolkit is properly installed." />

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="cs" original="MICROSOFT.VISUALSTUDIO.THREADING/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -79,6 +79,18 @@
<source>Nested semaphore requests must be released in LIFO order when the reentrancy setting is: '{0}'</source>
<target state="translated">Vnořené semaforové žádosti se musí být uvolňovat v pořadí LIFO, kdy nastavení vícenásobnému přístupu je: {0}</target>
</trans-unit>
<trans-unit id="SyncContextFrameMismatchedAffinity" translate="yes" xml:space="preserve">
<source>This frame has already been used with a different instance.</source>
<target state="new">This frame has already been used with a different instance.</target>
</trans-unit>
<trans-unit id="FrameMustBePushedFirst" translate="yes" xml:space="preserve">
<source>This instance must be pushed first.</source>
<target state="new">This instance must be pushed first.</target>
</trans-unit>
<trans-unit id="PushFromWrongThread" translate="yes" xml:space="preserve">
<source>Message pump can only be run from the original thread.</source>
<target state="new">Message pump can only be run from the original thread.</target>
</trans-unit>
</group>
</body>
</file>

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="de" original="MICROSOFT.VISUALSTUDIO.THREADING/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -79,6 +79,18 @@
<source>Nested semaphore requests must be released in LIFO order when the reentrancy setting is: '{0}'</source>
<target state="translated">Geschachtelte Semaphoranforderungen müssen in LIFO-Reihenfolge angegeben werden, wenn die Einstellung für die Eintrittsinvarianz "{0}" lautet.</target>
</trans-unit>
<trans-unit id="SyncContextFrameMismatchedAffinity" translate="yes" xml:space="preserve">
<source>This frame has already been used with a different instance.</source>
<target state="new">This frame has already been used with a different instance.</target>
</trans-unit>
<trans-unit id="FrameMustBePushedFirst" translate="yes" xml:space="preserve">
<source>This instance must be pushed first.</source>
<target state="new">This instance must be pushed first.</target>
</trans-unit>
<trans-unit id="PushFromWrongThread" translate="yes" xml:space="preserve">
<source>Message pump can only be run from the original thread.</source>
<target state="new">Message pump can only be run from the original thread.</target>
</trans-unit>
</group>
</body>
</file>

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="es" original="MICROSOFT.VISUALSTUDIO.THREADING/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -79,6 +79,18 @@
<source>Nested semaphore requests must be released in LIFO order when the reentrancy setting is: '{0}'</source>
<target state="translated">Las solicitudes de semáforo anidadas deben liberarse en orden LIFO cuando el valor de reentrada es "{0}"</target>
</trans-unit>
<trans-unit id="SyncContextFrameMismatchedAffinity" translate="yes" xml:space="preserve">
<source>This frame has already been used with a different instance.</source>
<target state="new">This frame has already been used with a different instance.</target>
</trans-unit>
<trans-unit id="FrameMustBePushedFirst" translate="yes" xml:space="preserve">
<source>This instance must be pushed first.</source>
<target state="new">This instance must be pushed first.</target>
</trans-unit>
<trans-unit id="PushFromWrongThread" translate="yes" xml:space="preserve">
<source>Message pump can only be run from the original thread.</source>
<target state="new">Message pump can only be run from the original thread.</target>
</trans-unit>
</group>
</body>
</file>

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="fr" original="MICROSOFT.VISUALSTUDIO.THREADING/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -79,6 +79,18 @@
<source>Nested semaphore requests must be released in LIFO order when the reentrancy setting is: '{0}'</source>
<target state="translated">Les demandes de sémaphore imbriquées doivent être diffusées dans l'ordre du dernier entré, premier sorti (LIFO) quand le paramètre de réentrance est : '{0}'</target>
</trans-unit>
<trans-unit id="SyncContextFrameMismatchedAffinity" translate="yes" xml:space="preserve">
<source>This frame has already been used with a different instance.</source>
<target state="new">This frame has already been used with a different instance.</target>
</trans-unit>
<trans-unit id="FrameMustBePushedFirst" translate="yes" xml:space="preserve">
<source>This instance must be pushed first.</source>
<target state="new">This instance must be pushed first.</target>
</trans-unit>
<trans-unit id="PushFromWrongThread" translate="yes" xml:space="preserve">
<source>Message pump can only be run from the original thread.</source>
<target state="new">Message pump can only be run from the original thread.</target>
</trans-unit>
</group>
</body>
</file>

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="it" original="MICROSOFT.VISUALSTUDIO.THREADING/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -79,6 +79,18 @@
<source>Nested semaphore requests must be released in LIFO order when the reentrancy setting is: '{0}'</source>
<target state="translated">Le richieste di semaforo nidificate devono essere rilasciate in base all'ordine LIFO quando l'impostazione di rientranza è: '{0}'</target>
</trans-unit>
<trans-unit id="SyncContextFrameMismatchedAffinity" translate="yes" xml:space="preserve">
<source>This frame has already been used with a different instance.</source>
<target state="new">This frame has already been used with a different instance.</target>
</trans-unit>
<trans-unit id="FrameMustBePushedFirst" translate="yes" xml:space="preserve">
<source>This instance must be pushed first.</source>
<target state="new">This instance must be pushed first.</target>
</trans-unit>
<trans-unit id="PushFromWrongThread" translate="yes" xml:space="preserve">
<source>Message pump can only be run from the original thread.</source>
<target state="new">Message pump can only be run from the original thread.</target>
</trans-unit>
</group>
</body>
</file>

Просмотреть файл

@ -79,6 +79,18 @@
<source>Nested semaphore requests must be released in LIFO order when the reentrancy setting is: '{0}'</source>
<target state="translated">入れ子になったセマフォの要求は、再入の設定が '{0}' である場合、LIFO の順序でリリースする必要があります。</target>
</trans-unit>
<trans-unit id="SyncContextFrameMismatchedAffinity" translate="yes" xml:space="preserve">
<source>This frame has already been used with a different instance.</source>
<target state="new">This frame has already been used with a different instance.</target>
</trans-unit>
<trans-unit id="FrameMustBePushedFirst" translate="yes" xml:space="preserve">
<source>This instance must be pushed first.</source>
<target state="new">This instance must be pushed first.</target>
</trans-unit>
<trans-unit id="PushFromWrongThread" translate="yes" xml:space="preserve">
<source>Message pump can only be run from the original thread.</source>
<target state="new">Message pump can only be run from the original thread.</target>
</trans-unit>
</group>
</body>
</file>

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="ko" original="MICROSOFT.VISUALSTUDIO.THREADING/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -79,6 +79,18 @@
<source>Nested semaphore requests must be released in LIFO order when the reentrancy setting is: '{0}'</source>
<target state="translated">재입력 설정이 '{0}'이면 중첩된 세마포 요청이 LIFO 순서로 릴리스되어야 합니다.</target>
</trans-unit>
<trans-unit id="SyncContextFrameMismatchedAffinity" translate="yes" xml:space="preserve">
<source>This frame has already been used with a different instance.</source>
<target state="new">This frame has already been used with a different instance.</target>
</trans-unit>
<trans-unit id="FrameMustBePushedFirst" translate="yes" xml:space="preserve">
<source>This instance must be pushed first.</source>
<target state="new">This instance must be pushed first.</target>
</trans-unit>
<trans-unit id="PushFromWrongThread" translate="yes" xml:space="preserve">
<source>Message pump can only be run from the original thread.</source>
<target state="new">Message pump can only be run from the original thread.</target>
</trans-unit>
</group>
</body>
</file>

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="pl" original="MICROSOFT.VISUALSTUDIO.THREADING/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -79,6 +79,18 @@
<source>Nested semaphore requests must be released in LIFO order when the reentrancy setting is: '{0}'</source>
<target state="translated">Żądania dotyczące zagnieżdżonego semafora muszą być zwalniane w kolejności LIFO, gdy ustawienie wielobieżności to: „{0}”</target>
</trans-unit>
<trans-unit id="SyncContextFrameMismatchedAffinity" translate="yes" xml:space="preserve">
<source>This frame has already been used with a different instance.</source>
<target state="new">This frame has already been used with a different instance.</target>
</trans-unit>
<trans-unit id="FrameMustBePushedFirst" translate="yes" xml:space="preserve">
<source>This instance must be pushed first.</source>
<target state="new">This instance must be pushed first.</target>
</trans-unit>
<trans-unit id="PushFromWrongThread" translate="yes" xml:space="preserve">
<source>Message pump can only be run from the original thread.</source>
<target state="new">Message pump can only be run from the original thread.</target>
</trans-unit>
</group>
</body>
</file>

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="pt-BR" original="MICROSOFT.VISUALSTUDIO.THREADING/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -79,6 +79,18 @@
<source>Nested semaphore requests must be released in LIFO order when the reentrancy setting is: '{0}'</source>
<target state="translated">Solicitações de semáforo aninhadas devem ser lançadas na ordem LIFO quando a configuração de novas entradas é: '{0}'</target>
</trans-unit>
<trans-unit id="SyncContextFrameMismatchedAffinity" translate="yes" xml:space="preserve">
<source>This frame has already been used with a different instance.</source>
<target state="new">This frame has already been used with a different instance.</target>
</trans-unit>
<trans-unit id="FrameMustBePushedFirst" translate="yes" xml:space="preserve">
<source>This instance must be pushed first.</source>
<target state="new">This instance must be pushed first.</target>
</trans-unit>
<trans-unit id="PushFromWrongThread" translate="yes" xml:space="preserve">
<source>Message pump can only be run from the original thread.</source>
<target state="new">Message pump can only be run from the original thread.</target>
</trans-unit>
</group>
</body>
</file>

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="ru" original="MICROSOFT.VISUALSTUDIO.THREADING/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -79,6 +79,18 @@
<source>Nested semaphore requests must be released in LIFO order when the reentrancy setting is: '{0}'</source>
<target state="translated">Вложенные запросы к семафорам должны освобождаться в порядке LIFO, если параметр повторного вхождения имеет значение: '{0}'</target>
</trans-unit>
<trans-unit id="SyncContextFrameMismatchedAffinity" translate="yes" xml:space="preserve">
<source>This frame has already been used with a different instance.</source>
<target state="new">This frame has already been used with a different instance.</target>
</trans-unit>
<trans-unit id="FrameMustBePushedFirst" translate="yes" xml:space="preserve">
<source>This instance must be pushed first.</source>
<target state="new">This instance must be pushed first.</target>
</trans-unit>
<trans-unit id="PushFromWrongThread" translate="yes" xml:space="preserve">
<source>Message pump can only be run from the original thread.</source>
<target state="new">Message pump can only be run from the original thread.</target>
</trans-unit>
</group>
</body>
</file>

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="tr" original="MICROSOFT.VISUALSTUDIO.THREADING/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -79,6 +79,18 @@
<source>Nested semaphore requests must be released in LIFO order when the reentrancy setting is: '{0}'</source>
<target state="translated">İç içe geçmiş semafor istekleri, yeniden giriş ayarı şu olduğunda LIFO sırasında yayınlanmalıdır: '{0}'</target>
</trans-unit>
<trans-unit id="SyncContextFrameMismatchedAffinity" translate="yes" xml:space="preserve">
<source>This frame has already been used with a different instance.</source>
<target state="new">This frame has already been used with a different instance.</target>
</trans-unit>
<trans-unit id="FrameMustBePushedFirst" translate="yes" xml:space="preserve">
<source>This instance must be pushed first.</source>
<target state="new">This instance must be pushed first.</target>
</trans-unit>
<trans-unit id="PushFromWrongThread" translate="yes" xml:space="preserve">
<source>Message pump can only be run from the original thread.</source>
<target state="new">Message pump can only be run from the original thread.</target>
</trans-unit>
</group>
</body>
</file>

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="zh-Hans" original="MICROSOFT.VISUALSTUDIO.THREADING/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -79,6 +79,18 @@
<source>Nested semaphore requests must be released in LIFO order when the reentrancy setting is: '{0}'</source>
<target state="translated">当可重入设置为: "{0}" 时, 嵌套的信号量请求必须以后进先出顺序释放</target>
</trans-unit>
<trans-unit id="SyncContextFrameMismatchedAffinity" translate="yes" xml:space="preserve">
<source>This frame has already been used with a different instance.</source>
<target state="new">This frame has already been used with a different instance.</target>
</trans-unit>
<trans-unit id="FrameMustBePushedFirst" translate="yes" xml:space="preserve">
<source>This instance must be pushed first.</source>
<target state="new">This instance must be pushed first.</target>
</trans-unit>
<trans-unit id="PushFromWrongThread" translate="yes" xml:space="preserve">
<source>Message pump can only be run from the original thread.</source>
<target state="new">Message pump can only be run from the original thread.</target>
</trans-unit>
</group>
</body>
</file>

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-US" target-language="zh-Hant" original="MICROSOFT.VISUALSTUDIO.THREADING/STRINGS.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -79,6 +79,18 @@
<source>Nested semaphore requests must be released in LIFO order when the reentrancy setting is: '{0}'</source>
<target state="translated">當重新進入設定是 '{0}' 時,巢狀旗號要求必須以 LIFO 順序發行</target>
</trans-unit>
<trans-unit id="SyncContextFrameMismatchedAffinity" translate="yes" xml:space="preserve">
<source>This frame has already been used with a different instance.</source>
<target state="new">This frame has already been used with a different instance.</target>
</trans-unit>
<trans-unit id="FrameMustBePushedFirst" translate="yes" xml:space="preserve">
<source>This instance must be pushed first.</source>
<target state="new">This instance must be pushed first.</target>
</trans-unit>
<trans-unit id="PushFromWrongThread" translate="yes" xml:space="preserve">
<source>Message pump can only be run from the original thread.</source>
<target state="new">Message pump can only be run from the original thread.</target>
</trans-unit>
</group>
</body>
</file>

Просмотреть файл

@ -157,6 +157,20 @@ namespace Microsoft.VisualStudio.Threading
/// <returns>A task that completes with the result of <paramref name="operation"/>, after the semaphore has been exited.</returns>
public abstract Task ExecuteAsync(Func<Task> operation, CancellationToken cancellationToken = default);
/// <summary>
/// Executes a given operation within the semaphore.
/// </summary>
/// <typeparam name="T">The type of value returned by the operation.</typeparam>
/// <param name="operation">
/// The delegate to invoke once the semaphore is entered. If a <see cref="JoinableTaskContext"/> was supplied to the constructor,
/// this delegate will execute on the main thread if this is invoked on the main thread, otherwise it will be invoked on the
/// threadpool. When no <see cref="JoinableTaskContext"/> is supplied to the constructor, this delegate will execute on the
/// caller's context.
/// </param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>A task that completes with the result of <paramref name="operation"/>, after the semaphore has been exited.</returns>
public abstract ValueTask<T> ExecuteAsync<T>(Func<ValueTask<T>> operation, CancellationToken cancellationToken = default);
/// <summary>
/// Conceals evidence that the caller has entered this <see cref="ReentrantSemaphore"/> till its result is disposed.
/// </summary>
@ -225,6 +239,20 @@ namespace Microsoft.VisualStudio.Threading
: semaphoreUser().ConfigureAwaitRunInline();
}
/// <summary>
/// Executes the semaphore request.
/// </summary>
/// <param name="semaphoreUser">The delegate that requests the semaphore and executes code within it.</param>
/// <returns>A value for the caller to await on.</returns>
private AwaitExtensions.ExecuteContinuationSynchronouslyAwaitable<T> ExecuteCoreAsync<T>(Func<Task<T>> semaphoreUser)
{
Requires.NotNull(semaphoreUser, nameof(semaphoreUser));
return this.joinableTaskFactory != null
? this.joinableTaskFactory.RunAsync(semaphoreUser).Task.ConfigureAwaitRunInline()
: semaphoreUser().ConfigureAwaitRunInline();
}
/// <summary>
/// A structure that hides any evidence that the caller has entered a <see cref="ReentrantSemaphore"/> till this value is disposed.
/// </summary>
@ -339,6 +367,68 @@ namespace Microsoft.VisualStudio.Threading
}
});
}
/// <inheritdoc />
public override async ValueTask<T> ExecuteAsync<T>(Func<ValueTask<T>> operation, CancellationToken cancellationToken = default)
{
Requires.NotNull(operation, nameof(operation));
// Note: this code is duplicated and not extracted to minimize allocating extra async state machines.
// For performance reasons in the JTF enabled scenario, we want to minimize the number of Joins performed, and also
// keep the size of the JoinableCollection to a minimum. This also means awaiting on the semaphore outside of a
// JTF.RunAsync. This requires us to not ConfigureAwait(true) on the semaphore. However, that prevents us from
// resuming on the correct sync context. To partially fix this, we will at least resume you on the main thread or
// thread pool.
AsyncSemaphore.Releaser releaser;
bool resumeOnMainThread = this.IsJoinableTaskAware ? this.joinableTaskCollection.Context.IsOnMainThread : false;
bool mustYield = false;
using (this.joinableTaskCollection?.Join())
{
if (this.IsJoinableTaskAware)
{
// Use ConfiguredAwaitRunInline() as ConfigureAwait(true) will
// deadlock due to not being inside a JTF.RunAsync().
var releaserTask = this.semaphore.EnterAsync(cancellationToken);
mustYield = !releaserTask.IsCompleted;
releaser = await releaserTask.ConfigureAwaitRunInline();
}
else
{
releaser = await this.semaphore.EnterAsync(cancellationToken).ConfigureAwait(true);
}
}
return await this.ExecuteCoreAsync(async delegate
{
try
{
if (this.IsJoinableTaskAware)
{
if (resumeOnMainThread)
{
// Return to the main thread if we started there.
await this.joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
}
else
{
await TaskScheduler.Default;
}
if (mustYield)
{
// Yield to prevent running on the stack that released the semaphore.
await Task.Yield();
}
}
return await operation();
}
finally
{
DisposeReleaserNoThrow(releaser);
}
});
}
}
/// <summary>
@ -445,6 +535,83 @@ namespace Microsoft.VisualStudio.Threading
});
}
/// <inheritdoc />
public override async ValueTask<T> ExecuteAsync<T>(Func<ValueTask<T>> operation, CancellationToken cancellationToken = default)
{
Requires.NotNull(operation, nameof(operation));
this.ThrowIfFaulted();
StrongBox<bool> ownedBox = this.reentrancyDetection.Value;
if (ownedBox?.Value ?? false)
{
throw Verify.FailOperation("Semaphore is already held and reentrancy setting is '{0}'.", ReentrancyMode.NotAllowed);
}
// Note: this code is duplicated and not extracted to minimize allocating extra async state machines.
// For performance reasons in the JTF enabled scenario, we want to minimize the number of Joins performed, and also
// keep the size of the JoinableCollection to a minimum. This also means awaiting on the semaphore outside of a
// JTF.RunAsync. This requires us to not ConfigureAwait(true) on the semaphore. However, that prevents us from
// resuming on the correct sync context. To partially fix this, we will at least resume you on the main thread or
// thread pool.
AsyncSemaphore.Releaser releaser;
bool resumeOnMainThread = this.IsJoinableTaskAware ? this.joinableTaskCollection.Context.IsOnMainThread : false;
bool mustYield = false;
using (this.joinableTaskCollection?.Join())
{
if (this.IsJoinableTaskAware)
{
// Use ConfiguredAwaitRunInline() as ConfigureAwait(true) will
// deadlock due to not being inside a JTF.RunAsync().
var releaserTask = this.semaphore.EnterAsync(cancellationToken);
mustYield = !releaserTask.IsCompleted;
releaser = await releaserTask.ConfigureAwaitRunInline();
}
else
{
releaser = await this.semaphore.EnterAsync(cancellationToken).ConfigureAwait(true);
}
}
return await this.ExecuteCoreAsync(async delegate
{
try
{
if (this.IsJoinableTaskAware)
{
if (resumeOnMainThread)
{
// Return to the main thread if we started there.
await this.joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
}
else
{
await TaskScheduler.Default;
}
if (mustYield)
{
// Yield to prevent running on the stack that released the semaphore.
await Task.Yield();
}
}
this.reentrancyDetection.Value = ownedBox = new StrongBox<bool>(true);
return await operation();
}
finally
{
// Make it clear to any forks of our ExecutionContexxt that the semaphore is no longer owned.
// Null check incase the switch to UI thread was cancelled.
if (ownedBox != null)
{
ownedBox.Value = false;
}
DisposeReleaserNoThrow(releaser);
}
});
}
/// <inheritdoc />
public override RevertRelevance SuppressRelevance()
{
@ -596,6 +763,118 @@ namespace Microsoft.VisualStudio.Threading
});
}
/// <inheritdoc />
public override async ValueTask<T> ExecuteAsync<T>(Func<ValueTask<T>> operation, CancellationToken cancellationToken = default)
{
Requires.NotNull(operation, nameof(operation));
this.ThrowIfFaulted();
// No race condition here: We're accessing AsyncLocal<T> which we by definition have our own copy of.
// Multiple threads or multiple async methods will all have their own storage for this field.
Stack<StrongBox<AsyncSemaphore.Releaser>> reentrantStack = this.reentrantCount.Value;
if (reentrantStack == null || reentrantStack.Count == 0)
{
// When the stack is empty, the semaphore isn't held. But many execution contexts that forked from a common root
// would be sharing this same empty Stack<T> instance. If we pushed to that Stack, all those forks would suddenly
// be seen as having entered this new top-level semaphore. We therefore allocate a new Stack and assign it to our
// AsyncLocal<T> field so that only this particular ExecutionContext is seen as having entered the semaphore.
this.reentrantCount.Value = reentrantStack = new Stack<StrongBox<AsyncSemaphore.Releaser>>(capacity: 2);
}
// Note: this code is duplicated and not extracted to minimize allocating extra async state machines.
// For performance reasons in the JTF enabled scenario, we want to minimize the number of Joins performed, and also
// keep the size of the JoinableCollection to a minimum. This also means awaiting on the semaphore outside of a
// JTF.RunAsync. This requires us to not ConfigureAwait(true) on the semaphore. However, that prevents us from
// resuming on the correct sync context. To partially fix this, we will at least resume you on the main thread or
// thread pool.
AsyncSemaphore.Releaser releaser;
bool resumeOnMainThread = this.IsJoinableTaskAware ? this.joinableTaskCollection.Context.IsOnMainThread : false;
bool mustYield = false;
if (reentrantStack.Count == 0)
{
using (this.joinableTaskCollection?.Join())
{
if (this.IsJoinableTaskAware)
{
// Use ConfiguredAwaitRunInline() as ConfigureAwait(true) will
// deadlock due to not being inside a JTF.RunAsync().
var releaserTask = this.semaphore.EnterAsync(cancellationToken);
mustYield = !releaserTask.IsCompleted;
releaser = await releaserTask.ConfigureAwaitRunInline();
}
else
{
releaser = await this.semaphore.EnterAsync(cancellationToken).ConfigureAwait(true);
}
}
}
else
{
releaser = default;
}
return await this.ExecuteCoreAsync(async delegate
{
bool pushed = false;
var pushedReleaser = new StrongBox<AsyncSemaphore.Releaser>(releaser);
try
{
if (this.IsJoinableTaskAware)
{
if (resumeOnMainThread)
{
// Return to the main thread if we started there.
await this.joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
}
else
{
await TaskScheduler.Default;
}
if (mustYield)
{
// Yield to prevent running on the stack that released the semaphore.
await Task.Yield();
}
}
// The semaphore faulted while we were waiting on it.
this.ThrowIfFaulted();
lock (reentrantStack)
{
reentrantStack.Push(pushedReleaser);
pushed = true;
}
return await operation();
}
finally
{
try
{
if (pushed)
{
lock (reentrantStack)
{
var poppedReleaser = reentrantStack.Pop();
if (!object.ReferenceEquals(poppedReleaser, pushedReleaser))
{
// When the semaphore faults, we will drain and throw for awaiting tasks one by one.
this.faulted = true;
throw Verify.FailOperation(Strings.SemaphoreStackNestingViolated, ReentrancyMode.Stack);
}
}
}
}
finally
{
DisposeReleaserNoThrow(releaser);
}
}
});
}
/// <inheritdoc />
public override RevertRelevance SuppressRelevance()
{
@ -727,6 +1006,100 @@ namespace Microsoft.VisualStudio.Threading
});
}
/// <inheritdoc />
public override async ValueTask<T> ExecuteAsync<T>(Func<ValueTask<T>> operation, CancellationToken cancellationToken = default)
{
Requires.NotNull(operation, nameof(operation));
this.ThrowIfFaulted();
// No race condition here: We're accessing AsyncLocal<T> which we by definition have our own copy of.
// Multiple threads or multiple async methods will all have their own storage for this field.
Stack<AsyncSemaphore.Releaser> reentrantStack = this.reentrantCount.Value;
if (reentrantStack == null || reentrantStack.Count == 0)
{
this.reentrantCount.Value = reentrantStack = new Stack<AsyncSemaphore.Releaser>(capacity: 2);
}
// Note: this code is duplicated and not extracted to minimize allocating extra async state machines.
// For performance reasons in the JTF enabled scenario, we want to minimize the number of Joins performed, and also
// keep the size of the JoinableCollection to a minimum. This also means awaiting on the semaphore outside of a
// JTF.RunAsync. This requires us to not ConfigureAwait(true) on the semaphore. However, that prevents us from
// resuming on the correct sync context. To partially fix this, we will at least resume you on the main thread or
// thread pool.
AsyncSemaphore.Releaser releaser;
bool resumeOnMainThread = this.IsJoinableTaskAware ? this.joinableTaskCollection.Context.IsOnMainThread : false;
bool mustYield = false;
if (reentrantStack.Count == 0)
{
using (this.joinableTaskCollection?.Join())
{
if (this.IsJoinableTaskAware)
{
// Use ConfiguredAwaitRunInline() as ConfigureAwait(true) will
// deadlock due to not being inside a JTF.RunAsync().
var releaserTask = this.semaphore.EnterAsync(cancellationToken);
mustYield = !releaserTask.IsCompleted;
releaser = await releaserTask.ConfigureAwaitRunInline();
}
else
{
releaser = await this.semaphore.EnterAsync(cancellationToken).ConfigureAwait(true);
}
}
}
else
{
releaser = default;
}
return await this.ExecuteCoreAsync(async delegate
{
bool pushed = false;
try
{
if (this.IsJoinableTaskAware)
{
if (resumeOnMainThread)
{
// Return to the main thread if we started there.
await this.joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
}
else
{
await TaskScheduler.Default;
}
if (mustYield)
{
// Yield to prevent running on the stack that released the semaphore.
await Task.Yield();
}
}
lock (reentrantStack)
{
reentrantStack.Push(releaser);
pushed = true;
releaser = default; // we should release whatever we pop off the stack (which ensures the last surviving nested holder actually releases).
}
return await operation();
}
finally
{
if (pushed)
{
lock (reentrantStack)
{
releaser = reentrantStack.Pop();
}
}
DisposeReleaserNoThrow(releaser);
}
});
}
/// <inheritdoc />
public override RevertRelevance SuppressRelevance()
{

Просмотреть файл

@ -0,0 +1,250 @@
/********************************************************
* *
* © Copyright (C) Microsoft. All rights reserved. *
* *
*********************************************************/
namespace Microsoft.VisualStudio.Threading
{
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
/// <summary>
/// A single-threaded synchronization context, akin to the DispatcherSynchronizationContext
/// and WindowsFormsSynchronizationContext.
/// </summary>
/// <remarks>
/// This must be created on the thread that will serve as the pumping thread.
/// </remarks>
public class SingleThreadedSynchronizationContext : SynchronizationContext
{
/// <summary>
/// The list of posted messages to be executed. Must be locked for all access.
/// </summary>
private readonly Queue<Message> messageQueue;
/// <summary>
/// The managed thread ID of the thread this instance owns.
/// </summary>
private readonly int ownedThreadId;
/// <summary>
/// Initializes a new instance of the <see cref="SingleThreadedSynchronizationContext"/> class,
/// with the new instance affinitized to the current thread.
/// </summary>
public SingleThreadedSynchronizationContext()
{
this.messageQueue = new Queue<Message>();
this.ownedThreadId = Environment.CurrentManagedThreadId;
}
/// <summary>
/// Initializes a new instance of the <see cref="SingleThreadedSynchronizationContext"/> class,
/// as an equivalent copy to another instance.
/// </summary>
private SingleThreadedSynchronizationContext(SingleThreadedSynchronizationContext copyFrom)
{
Requires.NotNull(copyFrom, nameof(copyFrom));
this.messageQueue = copyFrom.messageQueue;
this.ownedThreadId = copyFrom.ownedThreadId;
}
/// <inheritdoc/>
public override void Post(SendOrPostCallback d, object state)
{
var ctxt = ExecutionContext.Capture();
lock (this.messageQueue)
{
this.messageQueue.Enqueue(new Message(d, state, ctxt));
Monitor.PulseAll(this.messageQueue);
}
}
/// <inheritdoc/>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031", Justification = "We are catching it to rethrow elsewhere.")]
public override void Send(SendOrPostCallback d, object state)
{
Requires.NotNull(d, nameof(d));
if (this.ownedThreadId == Environment.CurrentManagedThreadId)
{
try
{
d(state);
}
catch (Exception ex)
{
throw new TargetInvocationException(ex);
}
}
else
{
Exception caughtException = null;
var evt = new ManualResetEventSlim();
var ctxt = ExecutionContext.Capture();
lock (this.messageQueue)
{
this.messageQueue.Enqueue(new Message(
s =>
{
try
{
d(state);
}
catch (Exception ex)
{
caughtException = ex;
}
finally
{
evt.Set();
}
},
null,
ctxt));
Monitor.PulseAll(this.messageQueue);
}
evt.Wait();
if (caughtException != null)
{
throw new TargetInvocationException(caughtException);
}
}
}
/// <inheritdoc/>
public override SynchronizationContext CreateCopy()
{
// Don't return "this", since that can result in the same instance being "Current"
// on another thread, and end up being misinterpreted as permission to skip the SyncContext
// and simply inline certain continuations by buggy code.
// See https://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/BaseCompatibilityPreferences.cs,39
return new SingleThreadedSynchronizationContext(this);
}
/// <summary>
/// Pushes a message pump on the current thread that will execute work scheduled using <see cref="Post(SendOrPostCallback, object)"/>.
/// </summary>
/// <param name="frame">The frame to represent this message pump, which controls when the message pump ends.</param>
public void PushFrame(Frame frame)
{
Requires.NotNull(frame, nameof(frame));
Verify.Operation(this.ownedThreadId == Environment.CurrentManagedThreadId, Strings.PushFromWrongThread);
frame.SetOwner(this);
using (this.Apply())
{
while (frame.Continue)
{
Message message;
lock (this.messageQueue)
{
// Check again now that we're holding the lock.
if (!frame.Continue)
{
break;
}
if (this.messageQueue.Count > 0)
{
message = this.messageQueue.Dequeue();
}
else
{
Monitor.Wait(this.messageQueue);
continue;
}
}
if (message.Context != null)
{
ExecutionContext.Run(
message.Context,
new ContextCallback(message.Callback),
message.State);
}
else
{
// If this throws, we intentionally let it propagate to our caller.
// WPF/WinForms SyncContexts will crash the process (perhaps by throwing from their method like this?).
// But anyway, throwing from here instead of crashing is more friendly IMO and more easily tested.
message.Callback(message.State);
}
}
}
}
private struct Message
{
internal readonly SendOrPostCallback Callback;
internal readonly object State;
internal readonly ExecutionContext Context;
internal Message(SendOrPostCallback d, object state, ExecutionContext ctxt)
{
this.Callback = d;
this.State = state;
this.Context = ctxt;
}
}
/// <summary>
/// A message pumping frame that may be pushed with <see cref="PushFrame(Frame)"/> to pump messages
/// on the owning thread.
/// </summary>
public class Frame
{
/// <summary>
/// The owning sync context.
/// </summary>
private SingleThreadedSynchronizationContext owner;
/// <summary>
/// Backing field for the <see cref="Continue" /> property.
/// </summary>
private bool @continue = true;
/// <summary>
/// Gets or sets a value indicating whether a call to <see cref="PushFrame(Frame)"/> with this <see cref="Frame"/>
/// should continue pumping messages or should return to its caller.
/// </summary>
public bool Continue
{
get
{
return this.@continue;
}
set
{
Verify.Operation(this.owner != null, Strings.FrameMustBePushedFirst);
this.@continue = value;
// Alert thread that may be blocked waiting for an incoming message
// that it no longer needs to wait.
if (!value)
{
lock (this.owner.messageQueue)
{
Monitor.PulseAll(this.owner.messageQueue);
}
}
}
}
internal void SetOwner(SingleThreadedSynchronizationContext context)
{
if (context != this.owner)
{
Verify.Operation(this.owner == null, Strings.SyncContextFrameMismatchedAffinity);
this.owner = context;
}
}
}
}
}

Просмотреть файл

@ -88,6 +88,15 @@ namespace Microsoft.VisualStudio.Threading {
}
}
/// <summary>
/// Looks up a localized string similar to This instance must be pushed first..
/// </summary>
internal static string FrameMustBePushedFirst {
get {
return ResourceManager.GetString("FrameMustBePushedFirst", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Already transitioned to the Completed state..
/// </summary>
@ -169,6 +178,15 @@ namespace Microsoft.VisualStudio.Threading {
}
}
/// <summary>
/// Looks up a localized string similar to Message pump can only be run from the original thread..
/// </summary>
internal static string PushFromWrongThread {
get {
return ResourceManager.GetString("PushFromWrongThread", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The queue is empty..
/// </summary>
@ -214,6 +232,15 @@ namespace Microsoft.VisualStudio.Threading {
}
}
/// <summary>
/// Looks up a localized string similar to This frame has already been used with a different instance..
/// </summary>
internal static string SyncContextFrameMismatchedAffinity {
get {
return ResourceManager.GetString("SyncContextFrameMismatchedAffinity", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The value factory has called for the value on the same instance..
/// </summary>

Просмотреть файл

@ -172,4 +172,13 @@
<data name="ValueFactoryReentrancy" xml:space="preserve">
<value>The value factory has called for the value on the same instance.</value>
</data>
<data name="FrameMustBePushedFirst" xml:space="preserve">
<value>This instance must be pushed first.</value>
</data>
<data name="PushFromWrongThread" xml:space="preserve">
<value>Message pump can only be run from the original thread.</value>
</data>
<data name="SyncContextFrameMismatchedAffinity" xml:space="preserve">
<value>This frame has already been used with a different instance.</value>
</data>
</root>

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше