Enhance VSTHRD003 to report any awaiting of tasks scheduled in another context
Fixes #255 Fixes #305
This commit is contained in:
Родитель
51e0db505c
Коммит
c6b1785933
|
@ -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());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -7,7 +7,7 @@ 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)
|
||||
[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) | Avoid using `Lazy<T>` where `T` is `Task<T2>` | Critical | [3rd rule](../threading_rules.md#Rule3)
|
||||
|
|
|
@ -128,6 +128,46 @@ class Tests
|
|||
this.VerifyCSharpDiagnostic(test, this.expect);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReportWarningWhenTaskIsReturnedDirectlyFromMethod()
|
||||
{
|
||||
var test = @"
|
||||
using System.Threading.Tasks;
|
||||
|
||||
class Tests
|
||||
{
|
||||
private Task task;
|
||||
|
||||
public Task GetTask()
|
||||
{
|
||||
return task;
|
||||
}
|
||||
}
|
||||
";
|
||||
var expect = this.CreateDiagnostic(10, 16, 4);
|
||||
this.VerifyCSharpDiagnostic(test, expect);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReportWarningWhenTaskIsReturnedAwaitedFromMethod()
|
||||
{
|
||||
var test = @"
|
||||
using System.Threading.Tasks;
|
||||
|
||||
class Tests
|
||||
{
|
||||
private Task<int> task;
|
||||
|
||||
public async Task<int> AwaitAndGetResult()
|
||||
{
|
||||
return await task;
|
||||
}
|
||||
}
|
||||
";
|
||||
var expect = this.CreateDiagnostic(10, 22, 4);
|
||||
this.VerifyCSharpDiagnostic(test, expect);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReportWarningWhenTaskTIsReturnedDirectlyWithCancellation()
|
||||
{
|
||||
|
@ -150,7 +190,30 @@ class Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void DoNotReportWarningWhenTaskTIsPassedAsArgument()
|
||||
public void 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;
|
||||
}
|
||||
";
|
||||
this.VerifyCSharpDiagnostic(test);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReportWarningWhenTaskTIsPassedAsArgumentAndTaskIsReturned()
|
||||
{
|
||||
var test = @"
|
||||
using System.Threading;
|
||||
|
@ -169,7 +232,8 @@ class Tests
|
|||
private static Task DoSomethingWith(Task t) => null;
|
||||
}
|
||||
";
|
||||
this.VerifyCSharpDiagnostic(test);
|
||||
var expect = this.CreateDiagnostic(12, 68, 4);
|
||||
this.VerifyCSharpDiagnostic(test, expect);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -270,6 +334,44 @@ class Tests
|
|||
this.VerifyCSharpDiagnostic(test);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoNotReportWarningWhenReturnedTaskIsDirectlyReturnedFromInvocation()
|
||||
{
|
||||
var test = @"
|
||||
using System.Threading.Tasks;
|
||||
|
||||
class Tests
|
||||
{
|
||||
public Task Test()
|
||||
{
|
||||
return SomeOperationAsync();
|
||||
}
|
||||
|
||||
public Task SomeOperationAsync() => Task.CompletedTask;
|
||||
}
|
||||
";
|
||||
this.VerifyCSharpDiagnostic(test);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoNotReportWarningWhenReturnedTaskIsAwaitedReturnedFromInvocation()
|
||||
{
|
||||
var test = @"
|
||||
using System.Threading.Tasks;
|
||||
|
||||
class Tests
|
||||
{
|
||||
public async Task<int> Test()
|
||||
{
|
||||
return await SomeOperationAsync();
|
||||
}
|
||||
|
||||
public Task<int> SomeOperationAsync() => Task.FromResult(3);
|
||||
}
|
||||
";
|
||||
this.VerifyCSharpDiagnostic(test);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoNotReportWarningWhenTaskIsDefinedWithinDelegateInSubblock()
|
||||
{
|
||||
|
@ -719,56 +821,6 @@ class Tests
|
|||
this.VerifyCSharpDiagnostic(test, this.expect);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoNotReportWarningForDerivedJoinableTaskFactoryWhenRunIsOverride()
|
||||
{
|
||||
var test = @"
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
|
||||
public class MyJoinableTaskFactory : JoinableTaskFactory
|
||||
{
|
||||
public MyJoinableTaskFactory(JoinableTaskFactory innerFactory) : base(innerFactory.Context)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
new public void Run(Func<System.Threading.Tasks.Task> asyncMethod)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Tests
|
||||
{
|
||||
public void Test()
|
||||
{
|
||||
MyJoinableTaskFactory myjtf = new MyJoinableTaskFactory(ThreadHelper.JoinableTaskFactory);
|
||||
|
||||
System.Threading.Tasks.Task<int> task = SomeOperationAsync();
|
||||
|
||||
myjtf.Run(async () =>
|
||||
{
|
||||
await task;
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<int> SomeOperationAsync()
|
||||
{
|
||||
await System.Threading.Tasks.Task.Delay(1000);
|
||||
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
|
||||
";
|
||||
|
||||
// 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
|
||||
this.VerifyCSharpDiagnostic(test);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReportWarningWhenAwaitingTaskInField()
|
||||
{
|
||||
|
|
|
@ -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>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
|
||||
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</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 translation’s 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 translation’s 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<Task<T>>.Value can deadlock.
|
||||
|
|
|
@ -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 einen Task innerhalb von JoinableTaskFactory.Run führt unter Umständen zu Deadlocks, wenn der Task außerhalb des Delegaten initialisiert wird.
|
||||
<source>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
|
||||
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</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 translation’s 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 translation’s 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<Task<T>>.Value can deadlock.
|
||||
|
|
|
@ -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 potenciales interbloqueos.
|
||||
<source>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
|
||||
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</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 translation’s 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 translation’s 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<Task<T>>.Value can deadlock.
|
||||
|
|
|
@ -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>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
|
||||
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
|
||||
<target state="needs-review-translation">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.
|
||||
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 translation’s 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 d’attendre des tâches non joignables dans des contextes de jointure</target>
|
||||
<source>Avoid awaiting foreign Tasks</source>
|
||||
<target state="needs-review-translation">Éviter d’attendre des tâches non joignables dans des contextes de jointure</target>
|
||||
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translation’s 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<Task<T>>.Value can deadlock.
|
||||
|
|
|
@ -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>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
|
||||
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</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 translation’s 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 attesa di 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 translation’s 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<Task<T>>.Value can deadlock.
|
||||
|
|
|
@ -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>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
|
||||
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
|
||||
<target state="needs-review-translation">タスクがデリゲート外部で初期化されている場合、JoinableTaskFactory.Run 内の Task で Await を呼び出すと、デッドロックが引き起こされる可能性があります。
|
||||
この問題は、タスクが確実にデリゲート内で初期化されるようにするか、Task の代わりに JoinableTask を使用することによって回避できます。</target>
|
||||
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translation’s 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 translation’s 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<Task<T>>.Value can deadlock.
|
||||
|
|
|
@ -63,14 +63,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>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
|
||||
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
|
||||
<target state="needs-review-translation">작업이 외부에서 초기화되면 대리자가 잠재적인 교착 상태를 일으킬 수 있으므로 JoinableTaskFactory.Run 내 Task에서 호출을 대기합니다.
|
||||
작업이 대리자 내에서 초기화되도록 하거나 Task 대신 JoinableTask를 사용하여 이 문제를 방지할 수 있습니다.</target>
|
||||
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translation’s 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 translation’s 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<Task<T>>.Value can deadlock.
|
||||
|
|
|
@ -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>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
|
||||
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</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 translation’s 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 translation’s 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<Task<T>>.Value can deadlock.
|
||||
|
|
|
@ -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>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
|
||||
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</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 translation’s 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 translation’s 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<Task<T>>.Value can deadlock.
|
||||
|
|
|
@ -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>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
|
||||
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
|
||||
<target state="needs-review-translation">При вызове await в объекте Task в JoinableTaskFactory.Run (и при условии, что задача инициализирована за пределами делегата), могут произойти взаимоблокировки.
|
||||
Вы можете избежать этой проблемы, инициализировав задачу в делегате или использовав JoinableTask вместо Task.</target>
|
||||
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translation’s 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 translation’s 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<Task<T>>.Value can deadlock.
|
||||
|
|
|
@ -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 kilitlenmelere neden olabilir.
|
||||
<source>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
|
||||
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</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 translation’s 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 translation’s 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<Task<T>>.Value can deadlock.
|
||||
|
|
|
@ -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>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
|
||||
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
|
||||
<target state="needs-review-translation">在委托外初始化任务时,对 JoinableTaskFactory.Run 内部 Task 调用 await 可能导致死锁。
|
||||
通过确保在委托内部初始化任务或使用 JoinableTask 取代 Task,可以避免此问题。</target>
|
||||
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translation’s 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 translation’s 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<Task<T>>.Value can deadlock.
|
||||
|
|
|
@ -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>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
|
||||
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</source>
|
||||
<target state="needs-review-translation">當工作在委派外部初始化時,在 JoinableTaskFactory.Run 內的 Task 上呼叫 await,可能會造成潛在的鎖死情形。
|
||||
您可以藉由確認工作在委派內初始化,或將 Task 改為使用 JoinableTask,來避免此問題。</target>
|
||||
<note from="MultilingualUpdate" annotates="source" priority="2">Please verify the translation’s 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 translation’s 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<Task<T>>.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 Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
|
||||
///You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task..
|
||||
/// </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 {
|
||||
|
|
|
@ -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>Calling await on a Task when the task is initialized in another context can cause potential deadlocks.
|
||||
You can avoid this problem by ensuring the task is initialized within the same method or by using JoinableTask instead of Task.</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="VSTHRD011_MessageFormat" xml:space="preserve">
|
||||
<value>Lazy<Task<T>>.Value can deadlock.
|
||||
|
|
|
@ -62,6 +62,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)
|
||||
|
@ -211,9 +230,12 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
|
|||
|
||||
internal static bool IsAsyncCompatibleReturnType(this ITypeSymbol typeSymbol)
|
||||
{
|
||||
return typeSymbol?.Name == nameof(Task) && typeSymbol.BelongsToNamespace(Namespaces.SystemThreadingTasks);
|
||||
// We could be more aggressive here and allow other types that implement the async method builder pattern.
|
||||
return IsTask(typeSymbol);
|
||||
}
|
||||
|
||||
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>
|
||||
|
@ -349,6 +371,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)
|
||||
|
@ -828,7 +882,7 @@ namespace Microsoft.VisualStudio.Threading.Analyzers
|
|||
{
|
||||
// Is the FromResult method on the Task or Task<T> class?
|
||||
var memberOwnerSymbol = semanticModel.GetSymbolInfo(originalSyntax).Symbol;
|
||||
if (memberOwnerSymbol?.ContainingType?.Name == nameof(Task) && memberOwnerSymbol.ContainingType.BelongsToNamespace(Namespaces.SystemThreadingTasks))
|
||||
if (IsTask(memberOwnerSymbol?.ContainingType))
|
||||
{
|
||||
var simplified = awaitedInvocation.ArgumentList.Arguments.Single().Expression;
|
||||
return simplified;
|
||||
|
|
|
@ -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,142 @@ 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.SemanticModel, 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.SemanticModel, 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.SemanticModel, 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, SemanticModel semanticModel, 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;
|
||||
}
|
||||
|
||||
if (semanticModel == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(semanticModel));
|
||||
}
|
||||
|
||||
SymbolInfo symbolToConsider = semanticModel.GetSymbolInfo(expressionSyntax, 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;
|
||||
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, semanticModel, 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;
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче