#c# #async-await
#c# #асинхронный-ожидание
Вопрос:
Я пытаюсь написать класс с методом, который будет получать некоторые данные из REST API (googleapi для плейлистов YouTube) и сбрасывать эти данные в ObservableCollection. Проблема, с которой я сталкиваюсь, заключается в том, что когда я вызываю это:
this.getPlaylistItemsAsync().GetAwaiter().GetResult();
код выполняется.
Когда я вызываю это:
await this.getPlaylistItemsAsync();
он просто пропускает код.
Я довольно новичок во всем асинхронном программировании… Я думаю, я понимаю основные понятия.. Я просто не могу понять, как это сделать: (
Полный код здесь:
#define DEBUG
//#define TestEnvi
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Newtonsoft.Json;
namespace IntuifaceYTPlaylist {
public class IFYT_Playlist {
public string playlistID {get;set;}= "PLYu7z3I8tdEmpjIoZHybXD4xmXXYrTqlk"; // The ID with which we identify our playlist
private string key = "supersecretAPIkey"; // The API-Key we use to access the google-API
public ObservableCollection<string> videosList {get;set;}= new ObservableCollection<string>();
public IFYT_Playlist () {
Console.WriteLine("Class Loaded!");
#if TestEnvi
this.getPlaylistItemsAsync().GetAwaiter().GetResult();
Console.WriteLine("This worked!");
await this.getPlaylistItemsAsync();
Console.WriteLine("This didn't!"); //In fact this line isn't even executed :(
#endif
}
#region getData
public async void update (){
this.getPlaylistItemsAsync().GetAwaiter().GetResult();
}
// Access the REST-API to retrieve the full dataset
// TODO: Manage connection to the API - DONE
// TODO: Parse JSON
public async Task<ObservableCollection<string>> getPlaylistItemsAsync(){
var output = new ObservableCollection<string>();
string URL = "https://www.googleapis.com/youtube/v3/playlistItems";
HttpClient client = new HttpClient();
string query = URL "?key=" this.key "amp;part=contentDetailsamp;playlistId=" this.playlistID "amp;maxResults=50";
var response = await client.GetStringAsync(query); //Dump the JSON string into an object!
# if DEBUG
// Dump the response into a file - just so we can check it if something goes wrong...
using (StreamWriter outputFile = new StreamWriter(Path.Combine("", "Dump.json")))
{
outputFile.WriteLine(response);
}
#endif
var responseData = JsonConvert.DeserializeObject<dynamic>(response);
// Iterate over the items in the list to get the VideoIDs of the individual videos
foreach (var item in responseData.items){
output.Add(JsonConvert.SerializeObject(item.contentDetails.videoId));
}
Console.WriteLine(output);
#if DEBUG //Let's see if that worked....
Console.WriteLine();
Console.WriteLine("Printing VideoIDs:");
Console.WriteLine();
foreach (var item in output){
Console.WriteLine(item);
}
Console.WriteLine();
#endif
this.videosList = output;
return output;
}
#endregion
}
}
Программа, которую я использую для вызова этого, выглядит следующим образом:
using System;
using IntuifaceYTPlaylist;
namespace TestEnvi
{
class Program
{
static void Main(string[] args)
{
Create();
}
static async void Create(){
IFYT_Playlist playlist = new IFYT_Playlist();
playlist.getPlaylistItemsAsync().GetAwaiter().GetResult();
Console.WriteLine("This worked!");
await playlist.getPlaylistItemsAsync();
Console.WriteLine("Did this?");
}
}
}
Комментарии:
1. Не могли бы вы пояснить, что вы подразумеваете под «пропусками по коду» в вашем случае? Я даже не вижу, где вы пытаетесь заполнить коллекцию результатами.
2.
async void
основная проблема здесь. Это «запустить и забыть». Как только вы достигнетеawait
внутри него, который еще не удовлетворен, он возвращается.3. Как это вообще компилируется? Вы вызываете
await
конструктор.4. он не выполняет код, а вместо этого отменяет выполнение программы без каких-либо сообщений.
5.
public async void update ()
это огромный красный флаг; у вас почти никогда не должно бытьasync void
метода — попробуйте сделать этоpublic async Task UpdateAsync()
и ожидать, если от вызывающего?
Ответ №1:
Вызванный метод немедленно возвращается к вызывающему (т. Е. К Main
) первому await
, и оттуда оба (вызывающий и вызываемый метод) выполняются одновременно. Тем не менее, поскольку Main
сначала завершается, вся программа завершается.
Таким образом, если вы хотите дождаться завершения асинхронного вызова, вы можете изменить Create()
на return Task
вместо void
, а затем вы можете подождать, например: Create().Wait();
Ответ №2:
Из вашего примера кода трудно определить, где именно вы пытаетесь обновить список, потому что код, на который вы указываете, даже не может быть скомпилирован. Вы не можете использовать await
ключевое слово в конструкторах, потому что конструкторы не могут быть async
.
Основная причина, по которой вы не видите вывод на консоль, заключается в том, что ваш Main
метод не ждет вашего async void Create
завершения, потому Create
что это асинхронная операция, и, как мы знаем, она не блокируется. Поэтому ваш основной поток просто запускает Create
операцию и продолжает выполнять другие действия, описанные ниже. Но ниже ничего нет, и все, программа выполнена.
Чтобы исправить это, вы должны либо превратить свой Main
метод в async
метод и вызвать await Create()
, либо просто вызвать Create().Wait
. Вы также можете вызвать Console.ReadLine
, если это консольное приложение.
Кстати, это работает с this.getPlaylistItemsAsync().GetAwaiter().GetResult();
, поскольку это больше не асинхронный код, ваш Create
метод заблокирован и ожидает getPlaylistItemsAsync
завершения. Аналогично, ваш Main
метод также ожидает Create
возврата, чтобы программа продолжала работать.
У вас также есть async void update
метод, который вы, вероятно, где-то используете (я не знаю), поэтому будьте осторожны с ним, поскольку у вас нет способа узнать, завершил ли метод свою работу или нет. Ниже приведено упрощенное объяснение асинхронного кода в C # и почему возврат void
из метода может быть плохим async
.
Немного об асинхронности
Способ, которым мы можем описать асинхронные операции, заключается в том, что операция не блокируется, что означает, что вы можете сказать, что хотите начать операцию и продолжить выполнение других действий, не дожидаясь завершения операции. Но это не очень полезно для простого запуска операции (иногда это может быть полезно), обычно вы хотите что-то сделать, когда операция завершена, сбой и т.д. Итак, в .NET асинхронная операция возвращает Task
or Task<T>
, которая является абстракцией над операцией, которая может быть завершена или может все еще выполняться. Затем вы можете прикрепить свой собственный код, который будет выполняться после task
завершения a .
Что происходит под капотом, когда вы ожидаете операции
Имейте в виду, что это описание слишком упрощено, чем то, что происходит на самом деле.
Поэтому, когда вы делаете что-то подобное:
public async Task IFYT_Playlist () {
await this.getPlaylistItemsAsync();
... Do some other stuff when the `getPlaylistItemsAsync` has been completed
}
IFYT_Playlist
Метод присоединяет приведенный ниже код await this.getPlaylistItemsAsync();
к задаче, которая возвращается методом, и немедленно возвращается. Опять же, это не то, что происходит на самом деле, но может дать вам основную идею:
public async Task IFYT_Playlist () {
Task resTask = this.getPlaylistItemsAsync();
return resTask.ContinueWith(t => ...Do some other stuff when the `getPlaylistItemsAsync` has been completed);
}
Так что, как оказалось, асинхронная операция должна возвращаться Task
, чтобы вызывающий мог присоединить к ней продолжение (с ContinueWith
помощью) метода. Теперь вы видите проблему? Если ваш возвращаемый тип равен void
, вызывающий не сможет присоединить код, который должен быть выполнен после завершения операции. Итак, если getPlaylistItemsAsync
возвращаемый тип равен void
, вы не можете await this.getPlaylistItemsAsync()
, потому что невозможно вызвать ContinueWith
nothing ( void
) . Поэтому вы даже не await
можете использовать метод with, который возвращает void
.
public async Task IFYT_Playlist () {
await this.getPlaylistItemsAsync(); // doesn't compile
}
Поэтому ваш update
метод должен возвращать Task
or Task<T>
для любого подходящего T
в вашем случае, а не nothing ( void
) .
Комментарии:
1. большое спасибо! итак, если я вас правильно понял, если я сделаю это: Task mytask = await methodThatReturnsTask(); Консоль. Writeline(«Foo»); он должен начать делать все, что есть в методе, а затем вернуться к вызывающему (в данном случае записать что-то на консоль) В моем примере этого не происходит?
2. То, что вы описали, больше похоже на синхронное выполнение, например, вы вызываете метод, ждете его завершения, а затем продолжаете делать все, что находится ниже вызова. В нашем случае, как только ваш метод встретит
await
ключевое слово, он вызовет метод справа отawait
(methodThatReturnsTask
) , затем дождется , пока метод вернет Task (который обычно выполняется) , затем присоединяет продолжение к task (Console.WriteLine("Foo")
) и затем возвращается обратно вызывающему. Сразу послеtask
завершения работы консоль. Будет выполнена строка записи («Foo»).3. Обратите внимание на различия между ожиданием завершения операции / метода и ожиданием возврата метода
Task
. Это очень разные вещи, в первом случае, если ваш метод выполняет какую-то трудоемкую задачу, вы будете ждать ее, и вы не сможете сделать ничего другого. Однако во втором случае вы ожидаете, что задача будет создана и возвращена, что должно быть быстрой операцией.4. Поэтому, если ваш метод
methodThatReturnsTask
выполняет некоторую трудоемкую операцию перед возвратом задачи, ваш код будет фактически заблокирован до техmethodThatReturnsTask
пор, пока не будет возвращена задача.