我无法理解等待/异步是如何工作的

时间:2017-04-16 13:13:03

标签: c# .net async-await

我开始研究Task,async / await概念是c#,我很难理解它,至少我不知道如何实现它。我开始重写我之前编写的旧测试程序,但现在我没有使用线程,而是希望使用这些新概念。基本上布局如下:

我有一个简单的类,我下载网页的HTML内容。 我在另一个类中处理它,我基本上只是将页面解析为我的模型。稍后我想将它显示到我的UI。 问题是我的程序没有响应,它在我处理信息时阻止了用户界面。

我2天前开始学习这个,我在线阅读了很多东西,包括MSDN和一些博客,但我还是无法弄明白。也许有人可以提供一个外观

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;     
}

我的解析类代码:

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代码:

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;
}    

对不起,如果它是微不足道的,但我无法弄清楚自己。

2 个答案:

答案 0 :(得分:1)

当进行异步时,您需要尝试一直异步,并避免使用异步调用混合阻塞调用。

您在事件处理程序中使用async void而没有await

尽管它是一个事件处理程序,但请尽量避免使用async void。应更新test12以返回Task并在事件处理程序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;
}  

另请考虑更新网络电话以使用HttpClient(如果有)。

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

参考: - Async/Await - Best Practices in Asynchronous Programming

答案 1 :(得分:0)

人们常常认为async-await意味着多个线程同时处理您的代码。除非您明确启动另一个线程,否则情况并非如此。

一个很好的比喻帮助我解释了async-await,这是this interview with Eric Lippert中使用的restauran隐喻。在中间某处搜索async-await。

Eric Lipperts将异步等待处理与一位不得不等待自己的水煮沸的厨师进行比较。他没有等待,而是环顾四周是否可以做其他事情。完成另一件事后,他回来看看水是否沸腾并开始处理沸水。

您的流程也是如此。只有一个线程忙(一次)。这个线程继续处理,直到他必须等待某事。这通常是一个相当长的过程,在不使用CPU核心的情况下处理,例如将文件写入磁盘,加载网页或从外部数据库查询信息。

你的线程一次只能做一件事。因此,当它忙于计算某些东西时,如果无法对操作员输入作出反应并且您的UI冻结,则直到计算完成。只有当你的线程有很多次等待其他进程完成时,异步等待才有用

如果调用异步函数,则可以确定该函数中的某个位置是等待的。事实上,如果你声明你的函数是异步的,并且忘了等待它,你的编译器会警告你。

当你的调用遇到函数中的await时,你的线程会调高其调用堆栈以查看它是否可以执行其他操作。如果您没有等待,可以继续处理,直到您必须等待。该线程再次上调其调用堆栈,以查看其中一个调用者是否在等待等等。

异步任务ReadDataAsync()    {         //做一些准备         使用(TextReader textReader = ...)         {             var myReadTask = textReader.ReadToEndAsync();             //而textReader正在等待信息可用             //你可以做其他事             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);
        ...

有一些规则要遵循:

  • 异步函数应该返回Task而不是void Task<TResult>而不是TResult
  • 有一个例外:异步事件处理程序返回void而不是Task
  • 在您的异步功能中,您应该等待某种程度。如果您没有等待,则声明您的函数async
  • 是没用的
  • await Task的结果无效,await Task<TResult>的结果为TResult
  • 如果您调用异步功能,请查看是否可以进行某些处理而不是等待通话结果

请注意,即使在等待它们之前调用多个异步函数,也不意味着多个线程同步运行这些函数。第一次调用异步函数后的语句在被调用函数开始等待后处理。

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);
    ...

通常,所有异步功能都由相同的上下文执行。在实践中,这意味着您可以编程,就好像您的程序是单线程的。这使您的程序外观更容易。

另一篇帮助我理解async-await的文章是Async-Await best practices written by the ever so helpful Stephen Cleary