Приветствую всех, сегодня поговорим о продолжении темы связанной с потоками, это часть 6. Читать Потоки ч.1 Читать Потоки ч.2 Читать Потоки ч.3 Читать потоки ч.4 Читать потоки ч.5
Вот мы и подобрались к завершающей части статей о потоках, сегодня мы рассмотрим работу async\await.
Ключевое слово async указывает компилятору, что метод, является асинхронным. await указывает компилятору, что в этой точке необходимо дождаться окончания асинхронной операции (при этом управление возвращается вызвавшему методу).
Оператор await заставляет метод, запускающий этот код, остановиться и дождаться завершения метода ShowAsync(), в итоге метод блокируется, пока пользователь не выберет какую-либо команду. В это время остальная программа продолжает отвечать на другие события. Как только метод ShowAsync() вернет управление, вызывавший его метод начнет работу с прерванной точки (хотя он может и дожидаться завершения возникших в промежутке событий). Метод, использующий оператор await, должен объявляться с модификатором async:
В async удобен тем что при возникновении исключительной ситуации, исключения выбрасывается в месте вызова асинхронной операции.
В этой теме мы рассмотрим как можно с помощью ключевых слов async\await использовать асинхронные методы, которые будут выполнятся асинхронно.
ВАЖНО! Стоит помнить что async\await в разных контекстах приложений используется по разному, к примеру в asp и wpf они имеют отличия в работе.
Простой пример работы с async await:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public static void Method() { for (int i = 0; i < 80; i++) { Thread.Sleep(100); Console.ForegroundColor = ConsoleColor.Yellow; Console.Write("-"); } } //помечаем ключевым словом async метод public async static void MethodAsync() { //Эта часть кода будет работать в основном потоке //создаем задачу передаем в делегат метод Method Task t = new Task(Method); //запускаем задачу t.Start(); //а вот эта часть завершится в 2 потоке //ожидаем завершения задачи await t; } static void Main(string[] args) { MethodAsync(); Console.WriteLine("Main завершился"); Console.ReadKey(); } |
С первого взгляда кажется ничего сложного, и возникает больше вопросов чем ответов, если посмотреть код метода MethodAsync вообще не понятно что делает async и для чего нужен вообще await. Данный пример не несет полезной нагрузки и мы могли обойтись без этих ключевых слов, ничего бы не изменилось, однако он демонстрирует работу async await. Все дело в том что эти слова позволяют компилятору указать метод, для которого будет произведена автоматическая генерация кода. Это создано для упрощения в разработке и уменьшения кода программы, вам не придется строчить кучу однотипного кода каждый раз, это все будет сделано за вас!
А теперь посмотрите как наш пример, выглядит под рефлектором, с генерировалось куча строк кода.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
using System; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp50 { internal class Prog { public static void Method() { for (int index = 0; index < 80; ++index) { Thread.Sleep(100); Console.ForegroundColor = ConsoleColor.Yellow; Console.Write("-"); } } [AsyncStateMachine(typeof(Prog.d__1))] [DebuggerStepThrough] public static void MethodAsync() { Prog.d__1 stateMachine = new Prog.d__1(); stateMachine.t__builder = AsyncVoidMethodBuilder.Create(); stateMachine.E1__state = -1; stateMachine.t__builder.Start < Prog.d__1 > (ref stateMachine); } private static void Main(string[] args) { Prog.MethodAsync(); Console.WriteLine("Main завершился"); Console.ReadKey(); } public Prog() { return; } [CompilerGenerated] private sealed class d__1 : IAsyncStateMachine { public int E1__state; public AsyncVoidMethodBuilder t__builder; private Task E5__1; private TaskAwaiter u__1; public d__1() { return; } void IAsyncStateMachine.MoveNext() { int num1 = this.E1__state; try { TaskAwaiter awaiter; int num2; if (num1 != 0) { // ISSUE: method pointer this.E5__1 = new Task(new Action(Method)); this.E5__1.Start(); awaiter = this.E5__1.GetAwaiter(); if (!awaiter.IsCompleted) { this.E1__state = num2 = 0; this.u__1 = awaiter; Prog.d__1 stateMachine = this; this.t__builder.AwaitUnsafeOnCompleted < TaskAwaiter, Prog.d__1 > (ref awaiter, ref stateMachine); return; } } else { awaiter = this.u__1; this.u__1 = new TaskAwaiter(); this.E1__state = num2 = -1; } awaiter.GetResult(); } catch (Exception ex) { this.E1__state = -2; this.t__builder.SetException(ex); return; } this.E1__state = -2; this.t__builder.SetResult(); } [DebuggerHidden] void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { } } } } |
Представленный выше код, я не стал исправлять, а скопировал его как он находился в рефлекторе. Давайте его разберем для большего понимания. Наши методы не поменялись, за исключением MethodAsync() и появился класс d__1 : IAsyncStateMachine . Внутри которого имеются методы MoveNext(); и SetStateMachine(); Внутри класса так же имеются поля, основное из них это public AsyncVoidMethodBuilder t__builder; который представляет конструктор для асинхронных методов, которые не возвращают никакое значение. Метод stateMachine.t__builder.Start < Prog.d__1 > (ref stateMachine); вызывает метод MoveNext(); Где происходит запуск задачи, нового потока и вызова метода Method(); Внутри метода MoveNext() так же имеется переменная которая ожидает завершения задачи, по окончанию работы метода, условия if (num1 != 0) становиться false и метод MoveNext(); завершает свою работу. Для понимания работы я вам советую скопировать код в студию и под отладкой посмотреть его работу.
В следующем примере я покажу как можно передать аргументы в метод, а так же получить из него результат:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public static string Method(object o) { for (int i = 0; i < 40; i++) { Thread.Sleep(100); Console.ForegroundColor = ConsoleColor.Yellow; Console.Write("-"); } return o+" nookery"; } //помечаем ключевым словом async метод public static async void MethodAsync() { //создаем задачу и запускаем ее, передав имя метода и аргумент. Task<string> t = Task<string>.Factory.StartNew(Method,"Hello"); //ожидаем завершения задачи и выводим результат Console.WriteLine(await t); } static void Main(string[] args) { MethodAsync(); Console.WriteLine("Main завершился"); Console.ReadKey(); } |
Когда вызывается Task. Factory, происходит обращение к статическому свойству класса Task, которое возвращает стандартный объект фабрики задач, т.е. TaskFactory. Назначение фабрики задач заключается в создании задач — в частности, трех видов задач:
- «обычных» задач (через метод StartNew);
- продолжений с множеством предшественников (через методы ContinueWhenAllи ContinueWhenAny);
- задач, которые являются оболочками для методов, следующих устаревшему шаблону АРМ
Следующий пример покажет как рассчитать сумму чисел в методе в отдельном потоке и вернуть результат в основной поток:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public static int Method(int x, int y) { return x + y; } async static Task<int> Start(int x,int y) { int resualt= await Task.Run(() => Method(x,y)); return resualt; } static void Main(string[] args) { //Используем свойство Result который возвращает нам результат вычислений Console.WriteLine("Сумма чисел: 3+4="+Start(3,4).Result); Console.WriteLine("Main завершился"); Console.ReadKey(); } |
Программисты считают, что модификатор async и оператор await вносят в код путаницу. Поэтому важно усвоить следующее.
- Если метод объявлен с модификатором async , это еще не означает, что он выполняется в асинхронном режиме. Это означает, что метод может содержать инструкции, которые могут выполняться в асинхронном режиме.
- Оператор await показывает, что метод должен быть запущен в отдельной задаче и что вызывающий код приостанавливается, пока не будет завершен вызов метода. Поток, используемый вызывающим кодом, высвобождается и может быть использован повторно. Это важно в том случае, если это тот самый поток, который используется пользовательским интерфейсом, поскольку это позволяет сохранить его отзывчивость на действия пользователя.
- Оператор await не является функциональным аналогом принадлежащего задаче метода Wait , который всегда блокирует текущий поток и не допускает его повторного использования, пока задача не завершится. Изначально код, возобновляющий выполнение после оператора await , пытается получить исходный поток, который был использован для вызова асинхронного метода. Если этот поток занят, код будет блокирован. Чтобы указать, что выполнение кода может быть возобновлено в любом доступном потоке, и сократить шансы на его блокировку, можно воспользоваться методом ConfigureAwait(false) . Особую пользу это принесет веб-приложениями сервисам, которым может понадобиться обслуживать многие тысячи одновременно поступающих запросов.
- Неосмотрительное использование асинхронных методов, возвращающих результаты и запускаемых в потоке пользовательского интерфейса, может привести к возникновению взаимных блокировок и стать причиной зависания приложения.
Продолжение следует…