rspec/rules/S4462/rule.adoc

80 lines
2.8 KiB
Plaintext
Raw Normal View History

== Why is this an issue?
Making blocking calls to ``++async++`` methods transforms code that was intended to be asynchronous into a blocking operation. Doing so can cause deadlocks and unexpected blocking of context threads.
2020-06-30 12:49:37 +02:00
2021-02-02 15:02:10 +01:00
2020-06-30 12:49:37 +02:00
According to the MSDN documentation:
2021-02-02 15:02:10 +01:00
2021-01-06 17:38:34 +01:00
____
The root cause of this deadlock is due to the way ``++await++`` handles contexts. By default, when an incomplete ``++Task++`` is awaited, the current “context” is captured and used to resume the method when the ``++Task++`` completes. This “context” is the current ``++SynchronizationContext++`` unless its null, in which case its the current ``++TaskScheduler++``. GUI and ASP.NET applications have a ``++SynchronizationContext++`` that permits only one chunk of code to run at a time. When the ``++await++`` completes, it attempts to execute the remainder of the ``++async++`` method within the captured context. But that context already has a thread in it, which is (synchronously) waiting for the ``++async++`` method to complete. Theyre each waiting for the other, causing a deadlock.
2020-06-30 12:49:37 +02:00
____
2020-06-30 12:49:37 +02:00
[frame=all]
[cols="^1,^1,^1"]
|===
|To Do This …|Instead of This …|Use This
|Retrieve the result of a background task|``++Task.Wait++``, ``++Task.Result++`` or ``++Task.GetAwaiter.GetResult++``|``++await++``
|Wait for any task to complete|``++Task.WaitAny++``|``++await Task.WhenAny++``
|Retrieve the results of multiple tasks|``++Task.WaitAll++``|``++await Task.WhenAll++``
|Wait a period of time|``++Thread.Sleep++``|``++await Task.Delay++``
|===
2020-06-30 12:49:37 +02:00
=== Noncompliant code example
2020-06-30 12:49:37 +02:00
2022-02-04 17:28:24 +01:00
[source,text]
2020-06-30 12:49:37 +02:00
----
public static class DeadlockDemo
{
private static async Task DelayAsync()
{
await Task.Delay(1000);
}
// This method causes a deadlock when called in a GUI or ASP.NET context.
public static void Test()
{
// Start the delay.
var delayTask = DelayAsync();
// Wait for the delay to complete.
delayTask.Wait(); // Noncompliant
}
}
----
=== Compliant solution
2020-06-30 12:49:37 +02:00
2022-02-04 17:28:24 +01:00
[source,text]
2020-06-30 12:49:37 +02:00
----
public static class DeadlockDemo
{
private static async Task DelayAsync()
{
await Task.Delay(1000);
}
public static async Task TestAsync()
{
// Start the delay.
var delayTask = DelayAsync();
// Wait for the delay to complete.
await delayTask;
}
}
----
=== Exceptions
2020-06-30 12:49:37 +02:00
* Main methods of Console Applications are not subject to this deadlock issue and so are ignored by this rule.
* ``++Thread.Sleep++`` is also ignored when it is used in a non-``++async++`` method.
2021-01-27 13:42:22 +01:00
* Calls chained after ``++Task.Run++`` or ``++Task.Factory.StartNew++`` are ignored because they don't suffer from this deadlock issue
2020-06-30 12:49:37 +02:00
== Resources
2020-06-30 12:49:37 +02:00
* https://msdn.microsoft.com/en-us/magazine/jj991977.aspx[Async/Await - Best Practices in Asynchronous Programming]