Ladybird
Ladybird

Reputation: 35

I cannot understand how exactly does await/async work

I started to look into Task, async/await concepts is c# and I'm having big problems understanding it, well at least i don't know how to implement it. I started rewriting an older test program i had written before, but now instead of threading i want to use these new concepts. Basically the layout is as it follows:

I have a simple class where i download the HTML content of a web page. I process that in another class where i basically just parse the page to my model. Later on i want to display that to my UI. The problem is that my program is not responsive, it blocks the UI while I'm processing the info.

I started learning this 2 days ago, i have read a lot of stuff online, including MSDN and some blogs but yet I'm unable to figure it out. Maybe someone can provide a look as well

HtmlDOwnloadCOde:

public async Task<string> GetMangaDescriptionPage(string detailUrl)
{
    WebClient client = new WebClient();

    Stream data = await client.OpenReadTaskAsync(detailUrl);
    StreamReader reader = new StreamReader(data);
    string s = reader.ReadToEnd();
    data.Dispose();
    reader.Dispose();
    data.Close();
    reader.Close();
    return s;     
}

My parse class code:

public async  Task<MangaDetailsModel> ParseMangaDescriptionPage()
{
    ParseOneManga pom = new ParseOneManga();
    string t1 = await pom.GetMangaDescriptionPage(selectedManga.url);
    HtmlDocument htmlDoc = new HtmlDocument();
        htmlDoc.LoadHtml(t1);
        var divs = htmlDoc.DocumentNode.Descendants("div").Where(x => x.Attributes.Contains("id") &&
            x.Attributes["id"].Value.Contains("title")).ToArray();
        mangaDetails.mangaName = divs[0].Element("h1").InnerText;

        mangaDetails.description = divs[0].Descendants("p").Single().InnerText ?? "DSA";
        var tds = divs[0].Descendants("td");
        int info = 0;

    var chapters = htmlDoc.DocumentNode.Descendants("div").Where(x => x.Attributes.Contains("id") &&
        x.Attributes["id"].Value.Contains("chapters")).ToArray();
    var chapterUi = chapters[0].Descendants("ul").Where(x => x.Attributes.Contains("class") &&
    x.Attributes["class"].Value.Contains("chlist"));
    foreach (var li in chapterUi)
    {
        var liChapter = li.Descendants("li");
        foreach (var h3tag in liChapter)
        {
            var chapterH3 = h3tag.Descendants("a").ToArray();
            SingleManagFox chapterData = new SingleManagFox();
            chapterData.name = chapterH3[1].InnerHtml;
            chapterData.url = chapterH3[1].GetAttributeValue("href", "0");
            mangaDetails.chapters.Add(chapterData);
        }
    };

    return mangaDetails;
}

UI code:

private async   void mainBtn_Click(object sender, RoutedEventArgs e)
{
    if (mangaList.SelectedItem != null)
    {
         test12((SingleManagFox)mangaList.SelectedItem);     
    }
}

private async void test12(SingleManagFox selectedManga)
{
    selectedManga = (SingleManagFox)mangaList.SelectedItem;
    MangaDetails mangaDetails = new MangaDetails(selectedManga);
    MangaDetailsModel mdm = await mangaDetails.ParseMangaDescriptionPage();
    txtMangaArtist.Text = mdm.artisName;
    txtMangaAuthor.Text = mdm.authorName;
    chapterList.ItemsSource = mdm.chapters;
}    

Sorry if its trivial but i cannot figure it out myself.

Upvotes: 3

Views: 244

Answers (2)

Harald Coppoolse
Harald Coppoolse

Reputation: 30512

Quite often people think that async-await means that multiple threads are processing your code at the same time. This is not the case, unless you explicitly start a different thread.

A good metaphore that helped me a lot explaining async-await is the restauran metaphor used in this interview with Eric Lippert. Search somewhere in the middle for async-await.

Eric Lipperts compares async-await processing with a cook who has to wait for his water to boil. Instead of waiting, he looks around if he can do other things instead. When finished doing the other thing, he comes back to see if the water is boiling and starts processing the boiling water.

The same is with your process. There is only one thread busy (at a time). This thread keeps processing until he has to await for something. This something is usually a fairly long process that is processed without using your CPU core, like writing a file to disk, loading a web page, or querying information from an external database.

Your thread can only do one thing at a time. So while it is busy calculating something, if can't react on operator input and your UI freezes, until the calculations are done. Async await will only help if there are a lot of times your thread would be waiting for other processes to complete

If you call an async function, you are certain that somewhere in that function is an await. In fact, if you declare your function async, and your forget to await in it, your compiler will warn you.

When your call meets the await in the function, your thread goes up its call stack to see if it can do other things. If you are not awaiting, you can continue processing, until you have to await. The thread goes up its call stack again to see if one of the callers is not awaiting etc.

async Task ReadDataAsync() { // do some preparations using (TextReader textReader = ...) { var myReadTask = textReader.ReadToEndAsync(); // while the textReader is waiting for the information to be available // you can do other things ProcessSomething();

        // after a while you really need the results from the read data,
        // so you await for it.
        string text = await MyReadTask;
        // after the await, the results from ReatToEnd are available
        Process(text);
        ...

There are some rules to follow:

  • an async function should return Task instead of void and Task<TResult> instead of TResult
  • There is one exception: the async event handler returns void instead of Task.
  • Inside your async function you should await somehow. If you don't await, it is useless to declare your function async
  • The result of await Task is void, and the result of await Task<TResult> is TResult
  • If you call an async function, see if you can do some processing instead of waiting for the results of the call

Note that even if you call several async functions before awaiting for them, does not mean that several threads are running these functions synchronously. The statement after your first call to the async function is processed after the called function starts awaiting.

async Task DoSomethingAsync()
{
    var task1 = ReadAsync(...);
    // no await, so next statement processes as soon as ReadAsync starts awaiting
    DoSomeThingElse();

    var task2 = QueryAsync(...);
    // again no await

    // now I need results from bothtask1, or from task2:
    await Task.WhenAll(new Task[] {task1, task2});

    var result1 = Task1.Result;
    var result2 = Task2.Result;
    Process(result1, result2);
    ...

Usually all your async functionality is performed by the same context. In practice this means that you can program as if your program is single threaded. This makes the look of your program much easier.

Another article that helped me a lot understanding async-await is Async-Await best practices written by the ever so helpful Stephen Cleary

Upvotes: 0

Nkosi
Nkosi

Reputation: 247571

When going async you need to try to go async all the way and avoid mixing blocking calls with async calls.

You are using async void in the event handler with no await.

Try to avoid async void unless it is an event handler. test12 should be updated to return Task and awaited in the event handler mainBtn_Click.

private async void mainBtn_Click(object sender, RoutedEventArgs e) {
    if (mangaList.SelectedItem != null) {
       await test12((SingleManagFox)mangaList.SelectedItem);
    }
}

private async Task test12(SingleManagFox selectedManga) {
    selectedManga = (SingleManagFox)mangaList.SelectedItem;
    MangaDetails mangaDetails = new MangaDetails(selectedManga);
    MangaDetailsModel mdm = await mangaDetails.ParseMangaDescriptionPage();
    txtMangaArtist.Text = mdm.artisName;
    txtMangaAuthor.Text = mdm.authorName;
    chapterList.ItemsSource = mdm.chapters;
}  

Also consider updating the web call to use HttpClient if available.

class ParseOneManga {
    public async Task<string> GetMangaDescriptionPageAsync(string detailUrl) {
        using (var client = new HttpClient()) {
            string s = await client.GetStringAsync(detailUrl);
            return s;                
        }
    }
}

Reference: - Async/Await - Best Practices in Asynchronous Programming

Upvotes: 1

Related Questions