Вот краткий ответ:
Чтобы ответить на этот вопрос, вам просто нужно понять, что делают ключевые слова async
/ await
.
Мы знаем, что один поток может делать только одну вещь за один раз, и мы также знаем, что один поток перенаправляет во всем приложении различные вызовы методов и события, ETC.Это означает, что когда поток должен идти дальше, он, скорее всего, запланирован или поставлен в очередь где-то за кулисами (но здесь я не буду объяснять эту часть). Когда поток вызывает метод, этот метод запускается до завершения перед любымдругие методы могут быть запущены, поэтому предпочтительнее отправлять долго работающие методы другим потокам, чтобы предотвратить зависание приложения.Чтобы разбить один метод на отдельные очереди, нам нужно выполнить какое-то необычное программирование ИЛИ вы можете поставить подпись async
на метод.Это говорит компилятору, что в какой-то момент метод может быть разбит на другие методы и помещен в очередь для последующего запуска.
Если это имеет смысл, то вы уже выясняете, что делает await
... await
сообщает компилятору, что именно здесь метод будет разбит и запланирован для запуска позже.Вот почему вы можете использовать ключевое слово async
без ключевого слова await
;хотя компилятор это знает и предупреждает.await
делает все это для вас, используя Task
.
Как await
использует Task
, чтобы сообщить компилятору запланировать оставшуюся часть метода?Когда вы вызываете await Task
, компиляторы вызывают для вас метод Task.GetAwaiter()
для этого Task
.GetAwaiter()
вернуть TaskAwaiter
.TaskAwaiter
реализует два интерфейса ICriticalNotifyCompletion, INotifyCompletion
.У каждого есть один метод, UnsafeOnCompleted(Action continuation)
и OnCompleted(Action continuation)
.Затем компилятор оборачивает оставшуюся часть метода (после ключевого слова await
) и помещает его в Action
, а затем вызывает методы OnCompleted
и UnsafeOnCompleted
и передает это Action
в качестве параметра.Теперь, когда Task
завершен, в случае успеха он вызывает OnCompleted
, а если нет, он вызывает UnsafeOnCompleted
, и он вызывает те из контекста потока, который использовался для запуска Task
.Он использует ThreadContext
для отправки потока в исходный поток.
Теперь вы можете понять, что ни async
, ни await
не выполняют никаких Task
с.Они просто говорят компилятору использовать какой-то заранее написанный код для планирования всего этого за вас.По факту;вы можете await
a Task
, который не запущен, и он будет await
до тех пор, пока Task
не будет выполнен и завершен, или пока приложение не завершится.
Зная это;давайте разберемся и поймем это глубже, выполнив то, что асинхронное ожидание делает вручную.
Использование асинхронного ожидания
using System;
using System.Threading.Tasks;
namespace Question_Answer_Console_App
{
class Program
{
static void Main(string[] args)
{
Test();
Console.ReadKey();
}
public static async void Test()
{
Console.WriteLine($"Before Task");
await DoWorkAsync();
Console.WriteLine($"After Task");
}
static public Task DoWorkAsync()
{
return Task.Run(() =>
{
Console.WriteLine($"{nameof(DoWorkAsync)} starting...");
Task.Delay(1000).Wait();
Console.WriteLine($"{nameof(DoWorkAsync)} ending...");
});
}
}
}
//OUTPUT
//Before Task
//DoWorkAsync starting...
//DoWorkAsync ending...
//After Task
Делая то, чтокомпилятор делает это вручную (вроде)
Примечание. Хотя этот код работает, он предназначен для того, чтобы помочь вам понять асинхронное ожидание с точки зрения сверху вниз.Он НЕ охватывает и не выполняет то же самое, что и компилятор дословно.
using System;
using System.Threading.Tasks;
namespace Question_Answer_Console_App
{
class Program
{
static void Main(string[] args)
{
Test();
Console.ReadKey();
}
public static void Test()
{
Console.WriteLine($"Before Task");
var task = DoWorkAsync();
var taskAwaiter = task.GetAwaiter();
taskAwaiter.OnCompleted(() => Console.WriteLine($"After Task"));
}
static public Task DoWorkAsync()
{
return Task.Run(() =>
{
Console.WriteLine($"{nameof(DoWorkAsync)} starting...");
Task.Delay(1000).Wait();
Console.WriteLine($"{nameof(DoWorkAsync)} ending...");
});
}
}
}
//OUTPUT
//Before Task
//DoWorkAsync starting...
//DoWorkAsync ending...
//After Task
РЕЗЮМЕ УРОКА:
Обратите внимание, что метод в моем примере DoWorkAsync()
это просто функция, которая возвращает Task
.В моем примере Task
работает, потому что в методе, который я использую return Task.Run(() =>…
.Использование ключевого слова await
не меняет эту логику.Это точно так же;await
делает только то, что я упомянул выше.
Если у вас есть какие-либо вопросы, просто задавайте, и я буду рад ответить на них.