加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

C#控制流程等待异步和线程

发布时间:2020-12-15 23:34:41 所属栏目:百科 来源:网络整理
导读:微软表示:“async和await关键字不会导致创建额外的线程.异步方法不需要多线程,因为异步方法不能在自己的线程上运行.该方法在当前同步上下文上运行,并仅在方法处于活动状态时在线程上使用时间.您可以使用Task.Run将CPU绑定的工作移动到后台线程,但后台线程对
微软表示:“async和await关键字不会导致创建额外的线程.异步方法不需要多线程,因为异步方法不能在自己的线程上运行.该方法在当前同步上下文上运行,并仅在方法处于活动状态时在线程上使用时间.您可以使用Task.Run将CPU绑定的工作移动到后台线程,但后台线程对于只等待结果可用的进程没有帮助.“

以下是Microsoft用于解释async和await使用的Web请求示例. (https://msdn.microsoft.com/en-us/library/mt674880.aspx).我在问题的最后粘贴了示例代码的相关部分.

我的问题是,在每个“var byteArray = await client.GetByteArrayAsync(url);”语句之后,控制返回到CreateMultipleTasksAsync方法,然后调用另一个ProcessURLAsync方法.在调用三次下载后,它会在完成第一个ProcessURLAsync方法后开始等待完成.但是,如果ProcessURLAsync没有在单独的线程中运行,它如何进入DisplayResults方法呢?因为如果它不在另一个线程上,在将控制权返回给CreateMultipleTasksAsync后,它永远无法完成.你能提供一个简单的控制流程,以便我能理解吗?

让我们假设第一个client.GetByteArrayAsync方法在Task download3 = ProcessURLAsync(..)之前完成,当时第一个DisplayResults被调用了吗?

private async void startButton_Click(object sender,RoutedEventArgs e)
    {
        resultsTextBox.Clear();
        await CreateMultipleTasksAsync();
        resultsTextBox.Text += "rnrnControl returned to startButton_Click.rn";
    }


    private async Task CreateMultipleTasksAsync()
    {
        // Declare an HttpClient object,and increase the buffer size. The
        // default buffer size is 65,536.
        HttpClient client =
            new HttpClient() { MaxResponseContentBufferSize = 1000000 };

        // Create and start the tasks. As each task finishes,DisplayResults 
        // displays its length.
        Task<int> download1 = 
            ProcessURLAsync("http://msdn.microsoft.com",client);
        Task<int> download2 = 
            ProcessURLAsync("http://msdn.microsoft.com/en-us/library/hh156528(VS.110).aspx",client);
        Task<int> download3 = 
            ProcessURLAsync("http://msdn.microsoft.com/en-us/library/67w7t67f.aspx",client);

        // Await each task.
        int length1 = await download1;
        int length2 = await download2;
        int length3 = await download3;

        int total = length1 + length2 + length3;

        // Display the total count for the downloaded websites.
        resultsTextBox.Text +=
            string.Format("rnrnTotal bytes returned:  {0}rn",total);
    }


    async Task<int> ProcessURLAsync(string url,HttpClient client)
    {
        var byteArray = await client.GetByteArrayAsync(url);
        DisplayResults(url,byteArray);
        return byteArray.Length;
    }


    private void DisplayResults(string url,byte[] content)
    {
        // Display the length of each website. The string format 
        // is designed to be used with a monospaced font,such as
        // Lucida Console or Global Monospace.
        var bytes = content.Length;
        // Strip off the "http://".
        var displayURL = url.Replace("http://","");
        resultsTextBox.Text += string.Format("n{0,-58} {1,8}",displayURL,bytes);
    }
}

解决方法

它在不创建新线程的情况下调用函数的方式是主“UI”线程不断地通过工作队列来一个接一个地处理队列中的项目.您可能听到的一个常见术语是“消息泵”.

当您执行等待并且您正在从UI线程运行时,一旦调用完成GetByteArrayAsync,新作业将被放入队列,当它成为该作业时,它将继续该方法的其余代码.

GetByteArrayAsync也不使用线程来完成它的工作,它要求操作系统完成工作并将数据加载到缓冲区然后它等待操作系统告诉它OS已经完成加载缓冲区.当该消息从操作系统进入时,一个新项目进入我之前讨论的那个队列(有点,我稍后再说),一旦它成为该项目,它将复制它从操作系统获得的小缓冲区更大的内部缓冲区并重复该过程.一旦它获得了文件的所有字节,它就会发出信号告知你的代码已经完成,导致你的代码将它继续放到队列中(我在上一段解释的内容).

我在谈论GetByteArrayAsync将项目放入队列时说“有点”的原因是程序中实际上有多个队列. UI有一个,一个用于“线程池”,另一个用于“I / O完成端口”(IOCP).线程池和IOCP将生成或重用池中的短期线程,因此可以将此技术称为创建线程,但是可用线程在池中空闲,不会创建线程.

您的代码将按原样使用“UI队列”,代码GetByteArrayAsync最有可能使用线程池队列来完成它的工作,操作系统使用该消息告诉GetByteArrayAsync数据在缓冲区中可用的消息使用IOCP队列.

您可以通过在执行await的行上添加.ConfigureAwait(false)来更改代码以从使用UI队列切换到线程池队列.

var byteArray = await client.GetByteArrayAsync(url).ConfigureAwait(false);

此设置告诉等待“而不是尝试使用SynchronizationContext.Current排队工作(如果您在UI线程上的UI队列)使用”默认“SynchronizationContext(这是线程池队列)

Let’s assume the first “client.GetByteArray??Async” method finished
before “Task download3 = ProcessURLAsync(..)” then,will it be “Task
download3 = ProcessURLAsync(..)” or “DisplayResults” that will be
invoked? Because as far as I understand,they will both be in the
queue you mention.

我将尝试对从鼠标单击到完成发生的所有事件做出明确的事件序列

>您在屏幕上单击鼠标
>操作系统使用IOCP池中的线程将WM_LBUTTONDOWN消息放入UI消息队列中.
> UI消息队列最终会到达该消息,并让所有控件都知道它.
>名为startButton的Button控件接收消息消息,发现事件被触发时鼠标位于自身上并调用其click事件处理程序
> click事件处理程序调用startButton_Click
> startButton_Click调用CreateMultipleTasksAsync
> CreateMultipleTasksAsync调用ProcessURLAsync
> ProcessURLAsync调用client.GetByteArrayAsync(url)
> GetByteArrayAsync最终在内部执行base.SendAsync(request,linkedCts.Token),
> SendAsync在内部执行大量操作,最终导致它从操作系统发送请求以从本机DLL下载文件.

到目前为止,没有发生任何“异步”,这只是所有正常的同步代码.到目前为止,如果它是同步或异步,则表现完全相同.

>一旦向操作系统发出请求,SendAsync将返回当前处于“正在运行”状态的任务.
>稍后在文件中它到达响应= await sendTask.ConfigureAwait(false);
> await检查任务的状态,发现它仍在运行并导致函数返回一个处于“正在运行”状态的新任务,它还要求任务在完成后运行一些额外的代码,但是使用线程池来做那个额外的代码(因为它使用了.ConfigureAwait(false)).
>重复此过程,直到最终GetByteArrayAsync返回任务< byte []>那就是“跑步”.
>你的await看到返回的Task< byte []>处于“正在运行”状态并导致函数返回一个新的任务< int>在“运行”状态下,它还要求任务< byte []>使用SynchronizationContext.Current运行一些额外的代码(因为你没有指定.ConfigureAwait(false)),这将导致在运行时将附加代码放入我们上次在步骤3中看到的队列.
> ProcessURLAsync返回Task< int>处于“正在运行”状态,该任务存储在变量download1中.
>步骤7-15再次重复变量download2和download3

注意:我们仍然在UI线程上,并且在整个过程中尚未将控制权交还给消息泵.

>等待下载1,它看到任务处于“正在运行”状态,它要求任务使用SynchronizationContext运行一些额外的代码.然后它创建一个处于“正在运行”状态的新任务并返回它.
>等待CreateMultipleTasksAsync的结果,它要求任务使用SynchronizationContext.Current运行一些额外的代码.因为该函数是异步void,它只是将控制权返回给消息泵.
>消息泵处理队列中的下一条消息.

好的,得到了??所有这些?现在我们继续讨论当“工作完成”时会发生什么

一旦你在任何时候执行步骤10操作系统可以使用IOCP发送消息告诉代码它已经完成归档缓冲区,那IOCP线程可以复制数据或者掩码请求线程池线程执行它(我没看足够深,看哪个).

此过程不断重复,直到所有数据都被下载,一旦完全下载,“额外代码”(委托)步骤12要求任务执行发送到SynchronizationContext.Post,因为它使用委托将执行的默认上下文线程池.在该委托的末尾,它将已返回的具有“正在运行”状态的原始任务发送到已完成状态.

一旦任务< byte []>在步骤13中返回,等待在步骤14中它执行它的SynchronizationContext.Post,此委托将包含类似于的代码

Delegate someDelegate () =>
{
    DisplayResults(url,byteArray);
    SetResultOfProcessURLAsyncTask(byteArray.Length);
}

因为您传入的上下文是UI上下文,所以此委托被放入要由UI处理的消息队列中,UI线程将在有机会时获取它.

一旦ProcessURLAsync for download1完成,将导致看起来有点像的委托

Delegate someDelegate () =>
{
    int length2 = await download2;
}

因为您传入的上下文是UI上下文,UI线程将在有机会时获取它.一旦完成,它会排队一个看起来有点像的代表

Delegate someDelegate () =>
{
    int length3 = await download3;
}

因为您传入的上下文是UI上下文,它会排队一个看起来有点像的委托

Delegate someDelegate () =>
{
    int total = length1 + length2 + length3;

    // Display the total count for the downloaded websites.
    resultsTextBox.Text +=
        string.Format("rnrnTotal bytes returned:  {0}rn",total);
    SetTaskForCreateMultipleTasksAsyncDone();
}

因为您传入的上下文是UI上下文,UI线程将在有机会时获取它.一旦调用“SetTaskForCreateMultipleTasksAsyncDone”,它就会排队一个看起来像的委托

Delegate someDelegate () =>
{
    resultsTextBox.Text += "rnrnControl returned to startButton_Click.rn";
}

你的工作终于完成了.

我做了一些重要的简化,并做了一些白色的谎言,使它更容易理解,但这是发生的事情的基本要点.当一个任务完成它的工作时,它将使用它正在处理的线程来执行SynchronizationContext.Post,该帖子将把它放入上下文所用的任何队列中,并将由处理队列的“泵”处理.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读