异步的世界【上】,上篇首要深入分析了async

上篇主要剖判了async\await此前的部分异步情势,昨天说异步的关键是指C#5的async\await异步。在此为了便利的公布,大家称async\await此前的异步为“旧异步”,async\await为“新异步”。

 

老大管理

有关新异步里面抛出拾分的正确姿势。我们先来看上边一段代码:

private async void button8_Click(object sender, EventArgs e)
{
    Task<string> task = GetUlrStringErr(null);
    Thread.Sleep(1000);//一段逻辑。。。。
    textBox1.Text = await task;
}

public async Task<string> GetUlrStringErr(string url)
{
    if (string.IsNullOrWhiteSpace(url))
    {
        throw new Exception("url不能为空");
    }
    using (HttpClient http = new HttpClient())
    {
        return await http.GetStringAsync(url);
    }
}

调治推行实行流程:

亿万先生 1

在实施完118行的时候依然从未把这些抛出来?那不是逆天了吗。非得在伺机await实行的时候才报错,显明119行的逻辑实行是一向不怎么意义的。让大家把卓绝提前抛出:

亿万先生 2

领到多个方式来做评释,那样就能够马上的抛出特别了。有朋友会说这么的太坑爹了呢,贰个表明还必须别的写个章程。接下来大家提供四个并未有这样坑爹的点子:

亿万先生 3

在异步函数里面用无名氏异步函数进行打包,同样能够落成即时验证。

备感也不及前种方法许多少…只是能如何做呢。

ConfigureAwait

除却AsyncHelper大家还足以接纳Task的ConfigureAwait方法来幸免死锁

private void button7_Click(object sender, EventArgs e)
{
    label1.Text = GetUlrString("https://github.com/").Result;
}

public async Task<string> GetUlrString(string url)
{
    using (HttpClient http = new HttpClient())
    {
        return await http.GetStringAsync(url).ConfigureAwait(false);
    }
}

ConfigureAwait的功力:使当前async方法的await后续操作无需复苏到主线程(不要求保存线程上下文)。

亿万先生 4

新异步的选拔

只能说新异步的行使太轻易(倘使仅仅只是说利用)

方法加上async修饰符,然后使用await关键字实践异步方法,就可以。对就是那般简约。像使用同步方法逻辑同样选取异步。

 public async Task<int> Test()
 {
     var num1 = await GetNumber(1);
     var num2 = await GetNumber(num1);
     var task =  GetNumber(num2);
     //或者
     var num3 = await task;
     return num1 + num2 + num3;
 }

Result的死锁陷阱

我们在分析UI单线程程序的时候说过,要慎用异步的Result属性。下边我们来剖判:

private void button4_Click(object sender, EventArgs e)
{
    label1.Text = GetUlrString("https://github.com/").Result;
}

public async Task<string> GetUlrString(string url)
{
    using (HttpClient http = new HttpClient())
    {
        return await http.GetStringAsync(url);
    }
}

代码 GetUlrString(“https://github.com/").Result 的Result属性会阻塞(占用)UI线程,而实践到GetUlrString方法的
await异步的时候又要释放UI线程。此时顶牛就来了,由于线程财富的抢占导致死锁。

且Result属性和.Wait()方法一致会堵塞线程。此等难点在Web服务程序里面一样存在。(差别:UI单次线程程序和web服务程序都会释放主线程,不一样的是Web服务线程不一定会回去原本的主线程,而UI程序一定会回到原先的UI线程)

作者们眼下说过,.net为何会如此智能的自发性释放主线程然后等待异步施行完成后又回来主线程是因为SynchronizationContext;k(SolutionItemsProject);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5.2);k(DevLang-csharp)&rd=true)的功劳。

但此处有个分化,那正是调整台程序里面是从未有过SynchronizationContext;k(SolutionItemsProject);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5.2);k(DevLang-csharp)&rd=true)的。所以这段代码放在调整台里面运营是不曾难点的。

static void Main(string[] args)
{
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    GetUlrString("https://github.com/").Wait();
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    Console.ReadKey();
}

public async static Task<string> GetUlrString(string url)
{
    using (HttpClient http = new HttpClient())
    {
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
        return await http.GetStringAsync(url);
    }
}

打字与印刷出来的都以同二个线程ID

对此Web后台服务程序

大概对于后台程序的熏陶未有单线程程序那么直观,但其股票总市值也是老大大的。且很几人对新异步存在误解。

【误解】:新异步能够进步Web程序的性质。

【正解】:异步不会进级单次必要结果的时间,可是能够拉长Web程序的吞吐量。

1、为啥不会进级单次供给结果的光阴?

实在我们从地点示例代码(尽管是UI程序的代码)也足以观望。

 亿万先生 5

2、为何能够增加Web程序的吞吐量?

那怎样是吞吐量呢,也便是本来只能10个人还要做客的网址现在得以二十私家同不常间做客了。约等于常说的并发量。

要么用地点的代码来讲明。[代码2]
阻塞了UI线程等待央浼结果,所以UI线程被占用,而[代码3]运用了新的线程央浼,所以UI线程未有被占用,而能够延续响应UI分界面。

那难题来了,大家的Web程序原始就是十六线程的,且web线程都以跑的线程池线程(使用线程池线程是为着制止不断创制、销毁线程所变成的能源资金财产浪费),而线程池线程可使用线程数量是必定的,尽管能够设置,但它依旧会在必然限制内。如此一来,我们web线程是宝贵的(物以稀为贵),不可能滥用。用完了,那么别的用户哀告的时候就不只怕管理间接503了。

那怎样算是滥用呢?比方:文件读取、URubiconL央浼、数据库访问等IO央浼。若是用web线程来做这几个耗费时间的IO操作那么就能够卡住web线程,而web线程阻塞得多了web线程池线程就远远不足用了。也就高达了web程序最大访问数。

那时大家的新异步破土而出,解放了那个原来管理IO需要而堵塞的web线程(想偷懒?没门,干活了。)。通过异步形式接纳相对廉价的线程(非web线程池线程)来拍卖IO操作,那样web线程池线程就足以解放出来管理越来越多的央求了。

不信?下边大家来测量试验下:

【测量检验步骤】:

1、新建一个web api项目 

2、新建贰个多少访谈类,分别提供一块、异步方法(在措施逻辑实行前后读取时间、线程id、web线程池线程使用数)

public class GetDataHelper
{
    /// <summary>
    /// 同步方法获取数据
    /// </summary>
    /// <returns></returns>
    public string GetData()
    {
        var beginInfo = GetBeginThreadInfo();
        using (HttpClient http = new HttpClient())
        {
            http.GetStringAsync("https://github.com/").Wait();//注意:这里是同步阻塞
        }
        return beginInfo + GetEndThreadInfo();
    }

    /// <summary>
    /// 异步方法获取数据
    /// </summary>
    /// <returns></returns>
    public async Task<string> GetDataAsync()
    {
        var beginInfo = GetBeginThreadInfo();
        using (HttpClient http = new HttpClient())
        {
            await http.GetStringAsync("https://github.com/");//注意:这里是异步等待
        }
        return beginInfo + GetEndThreadInfo();
    }

    public string GetBeginThreadInfo()
    {
        int t1, t2, t3;
        ThreadPool.GetAvailableThreads(out t1, out t3);
        ThreadPool.GetMaxThreads(out t2, out t3);
        return string.Format("开始:{0:mm:ss,ffff} 线程Id:{1} Web线程数:{2}",
                                DateTime.Now,
                                Thread.CurrentThread.ManagedThreadId,                                  
                                t2 - t1);
    }

    public string GetEndThreadInfo()
    {
        int t1, t2, t3;
        ThreadPool.GetAvailableThreads(out t1, out t3);
        ThreadPool.GetMaxThreads(out t2, out t3);
        return string.Format(" 结束:{0:mm:ss,ffff} 线程Id:{1} Web线程数:{2}",
                                DateTime.Now,
                                Thread.CurrentThread.ManagedThreadId,
                                t2 - t1);
    }
}

3、新建一个web api调整器

[HttpGet]
public async Task<string> Get(string str)
{
    GetDataHelper sqlHelper = new GetDataHelper();
    switch (str)
    {
        case "异步处理"://
            return await sqlHelper.GetDataAsync();
        case "同步处理"://
            return sqlHelper.GetData();
    }
    return "参数不正确";           
}       

4、发表web
api程序,布署到地头iis(联手链接http://localhost:803/api/Home?str=同步处理 
异步链接http://localhost:803/api/Home?str=异步处理

5、接着上边的winform程序里面测验央浼:(同临时候提倡12个央求)

亿万先生 6亿万先生 7

private void button6_Click(object sender, EventArgs e)
{
    textBox1.Text = "";
    label1.Text = "";
    Task.Run(() =>
    {
        TestResultUrl("http://localhost:803/api/Home?str=同步处理");
    });
}

private void button5_Click(object sender, EventArgs e)
{
    textBox1.Text = "";
    label1.Text = "";
    Task.Run(() =>
    {
        TestResultUrl("http://localhost:803/api/Home?str=异步处理");
    });
}

public void TestResultUrl(string url)
{
    int resultEnd = 0;
    HttpClient http = new HttpClient();

    int number = 10;
    for (int i = 0; i < number; i++)
    {
        new Thread(async () =>
        {
            var resultStr = await http.GetStringAsync(url);
            label1.Invoke((Action)(() =>
            {
                textBox1.AppendText(resultStr.Replace(" ", "\r\t") + "\r\n");
                if (++resultEnd >= number)
                {
                    label1.Text = "全部执行完毕";
                }
            }));

        }).Start();
    }
}

View Code

6、重启iis,并用浏览器访谈一回要哀求的链接地址(预热)

7、运维winform程序,点击“访谈同步达成的Web”:

亿万先生 8

亿万先生 9

8、重复6,然后再次起动winform程序点击“访谈异步完结的Web”

亿万先生 10

拜候这么些多少有哪些感想?

数据和大家前面的【正解】完全合乎。留心观察,各样单次央浼用时基本上相差极小。
可是步骤7″同步完成”最高投入web线程数是10,而步骤8“异步完毕”最高投入web线程数是3。

也正是说“异步达成”使用越来越少的web线程完成了平等的乞请数量,如此一来大家就有越来越多剩余的web线程去管理更加多用户发起的呼吁。

接着我们还开掘一块达成诉求前后的线程ID是同样的,而异步达成上下线程ID不自然一致。再一次表明试行await异步前释放了主线程。

【结论】:

  • 选取新异步能够升高Web服务程序的吞吐量
  • 对于客户端的话,web服务的异步并不会增加客户端的单次访谈速度。
  • 试行新异步前会释放web线程,而等待异步实施到位后又回来了web线程上。从而抓实web线程的利用率。

【图解】:

亿万先生 11

选择AsyncHelper在共同代码里面调用异步

但唯独,可然则,大家亟须在一同方法里面施行异步怎办?办法认定是有个别

咱俩先是定义二个AsyncHelper静态类:

static class AsyncHelper
{
    private static readonly TaskFactory _myTaskFactory = new TaskFactory(CancellationToken.None,
        TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);

    public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        return _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
    }

    public static void RunSync(Func<Task> func)
    {
        _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
    }
}

然后调用异步:

private void button7_Click(object sender, EventArgs e)
{
    label1.Text = AsyncHelper.RunSync(() => GetUlrString("https://github.com/"));
}

这么就不会死锁了。

ConfigureAwait

除去AsyncHelper大家还足以利用Task的ConfigureAwait方法来制止死锁

private void button7_Click(object sender, EventArgs e)
{
    label1.Text = GetUlrString("https://github.com/").Result;
}

public async Task<string> GetUlrString(string url)
{
    using (HttpClient http = new HttpClient())
    {
        return await http.GetStringAsync(url).ConfigureAwait(false);
    }
}

ConfigureAwait的功能:使当前async方法的await后续操作无需复苏到主线程(无需保存线程上下文)。

亿万先生 12

新异步的应用

不得不说新异步的采取太轻松(假使仅仅只是说利用)

方式加上async修饰符,然后使用await关键字施行异步方法,就可以。对正是这么简约。像使用同步方法逻辑同样选用异步。

 public async Task<int> Test()
 {
     var num1 = await GetNumber(1);
     var num2 = await GetNumber(num1);
     var task =  GetNumber(num2);
     //或者
     var num3 = await task;
     return num1 + num2 + num3;
 }

Result的死锁陷阱

我们在剖判UI单线程程序的时候说过,要慎用异步的Result属性。上面我们来深入分析:

private void button4_Click(object sender, EventArgs e)
{
    label1.Text = GetUlrString("https://github.com/").Result;
}

public async Task<string> GetUlrString(string url)
{
    using (HttpClient http = new HttpClient())
    {
        return await http.GetStringAsync(url);
    }
}

代码 GetUlrString(“https://github.com/").Result 的Result属性会阻塞(占用)UI线程,而进行到GetUlrString方法的
await异步的时候又要释放UI线程。此时抵触就来了,由于线程财富的侵吞导致死锁。

且Result属性和.Wait()方法一样会阻塞线程。此等难题在Web服务程序里面同样存在。(不相同:UI单次线程程序和web服务程序都会放出主线程,不一样的是Web服务线程不一定会回到原先的主线程,而UI程序一定会再次回到原来的UI线程)

咱俩后边说过,.net为啥会这么智能的活动释放主线程然后等待异步施行完成后又回去主线程是因为SynchronizationContext;k(SolutionItemsProject);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5.2);k(DevLang-csharp)&rd=true)的功劳。

但这里有个例外,那就是调控台程序里面是未曾SynchronizationContext;k(SolutionItemsProject);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5.2);k(DevLang-csharp)&rd=true)的。所以这段代码放在调节台里面运营是平素不难点的。

static void Main(string[] args)
{
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    GetUlrString("https://github.com/").Wait();
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    Console.ReadKey();
}

public async static Task<string> GetUlrString(string url)
{
    using (HttpClient http = new HttpClient())
    {
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
        return await http.GetStringAsync(url);
    }
}

打字与印刷出来的都以同二个线程ID

接上篇:《C#异步的社会风气【上】

行使AsyncHelper在一齐代码里面调用异步

但只是,可然而,大家务必在同步方法里面实行异步怎办?办法显著是有的

我们首先定义三个AsyncHelper静态类:

static class AsyncHelper
{
    private static readonly TaskFactory _myTaskFactory = new TaskFactory(CancellationToken.None,
        TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);

    public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        return _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
    }

    public static void RunSync(Func<Task> func)
    {
        _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
    }
}

下一场调用异步:

private void button7_Click(object sender, EventArgs e)
{
    label1.Text = AsyncHelper.RunSync(() => GetUlrString("https://github.com/"));
}

如此就不会死锁了。

异步的贯彻

地点轻巧剖析了新异步技术和总体性。接下来让我们传承揭秘异步的实质,神秘的半袖上边究竟是怎么落到实处的。

第一大家编辑三个用来反编写翻译的演示:

class MyAsyncTest
{
    public async Task<string> GetUrlStringAsync(HttpClient http, string url, int time)
    {
        await Task.Delay(time);
        return await http.GetStringAsync(url);
    }
}

反编写翻译代码:

点击看大图

为了便利阅读,我们把编写翻译器自动命名的门类重命名。

亿万先生, GetUrlStringAsync 方法成为了那般颜值:

public Task<string> GetUrlStringAsync(HttpClient http, string url, int time)
{
    GetUrlStringAsyncdStateMachine stateMachine = new GetUrlStringAsyncdStateMachine()
    {
        _this = this,
        http = http,
        url = url,
        time = time,
        _builder = AsyncTaskMethodBuilder<string>.Create(),
        _state = -1
    };
    stateMachine._builder.Start(ref stateMachine);
    return stateMachine._builder.Task;
}

方法签字完全一致,只是在那之中的剧情产生了一个境况机 GetUrlStringAsyncdStateMachine
 的调用。此状态机就是编写翻译器自动创造的。上面来拜访神秘的状态机是何等鬼:

private sealed class GetUrlStringAsyncdStateMachine : IAsyncStateMachine
{
    public int _state;
    public MyAsyncTest _this;
    private string _str1;
    public AsyncTaskMethodBuilder<string> _builder;
    private TaskAwaiter taskAwaiter1;
    private TaskAwaiter<string> taskAwaiter2;

    //异步方法的三个形参都到这里来了
    public HttpClient http;
    public int time;
    public string url;

    private void MoveNext()
    {
        string str;
        int num = this._state;
        try
        {
            TaskAwaiter awaiter;
            MyAsyncTest.GetUrlStringAsyncdStateMachine d__;
            string str2;
            switch (num)
            {
                case 0:
                    break;

                case 1:
                    goto Label_00CD;

                default:
                    //这里是异步方法 await Task.Delay(time);的具体实现
                    awaiter = Task.Delay(this.time).GetAwaiter();
                    if (awaiter.IsCompleted)
                    {
                        goto Label_0077;
                    }
                    this._state = num = 0;
                    this.taskAwaiter1 = awaiter;
                    d__ = this;
                    this._builder.AwaitUnsafeOnCompleted<TaskAwaiter, MyAsyncTest.GetUrlStringAsyncdStateMachine>(ref awaiter, ref d__);
                    return;
            }
            awaiter = this.taskAwaiter1;
            this.taskAwaiter1 = new TaskAwaiter();
            this._state = num = -1;
        Label_0077:
            awaiter.GetResult();
            awaiter = new TaskAwaiter();
            //这里是异步方法await http.GetStringAsync(url);的具体实现
            TaskAwaiter<string> awaiter2 = this.http.GetStringAsync(this.url).GetAwaiter();
            if (awaiter2.IsCompleted)
            {
                goto Label_00EA;
            }
            this._state = num = 1;
            this.taskAwaiter2 = awaiter2;
            d__ = this;
            this._builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, MyAsyncTest.GetUrlStringAsyncdStateMachine>(ref awaiter2, ref d__);
            return;
        Label_00CD:
            awaiter2 = this.taskAwaiter2;
            this.taskAwaiter2 = new TaskAwaiter<string>();
            this._state = num = -1;
        Label_00EA:
            str2 = awaiter2.GetResult();
            awaiter2 = new TaskAwaiter<string>();
            this._str1 = str2;
            str = this._str1;
        }
        catch (Exception exception)
        {
            this._state = -2;
            this._builder.SetException(exception);
            return;
        }
        this._state = -2;
        this._builder.SetResult(str);
    }

    [DebuggerHidden]
    private void SetStateMachine(IAsyncStateMachine stateMachine)
    {
    }

}

生硬多少个异步等待实践的时候纵然在每每调用状态机中的MoveNext()方法。经验来至大家前边深入分析过的IEumerable,可是前天的那么些肯定复杂度要高于在此在此之前的要命。测度是那样,大家照旧来注解下实际:

在开局方法 GetUrlStringAsync 第一次开发银行状态机 stateMachine._builder.Start(ref stateMachine); 

亿万先生 13

 确实是调用了 MoveNext 。因为_state的发轫值是-1,所以进行到了上面包车型地铁位置:

亿万先生 14

绕了一圈又回去了 MoveNext 。由此,我们得以现象成多少个异步调用正是在时时随地实行MoveNext直到结束。

说了这么久有哪些意思啊,就如忘记了大家的目标是要通过此前编写的测量试验代码来解析异步的试行逻辑的。

再度贴出从前的测量试验代码,以防忘记了。

亿万先生 15

反编写翻译后代码执行逻辑图:

亿万先生 16

当然那只是大概非常的大的实施流程,但也会有 awaiter.Iscompleted 为 true 的景色。别的可能的留着我们温馨去雕饰吧。 

 

本文已协同至索引目录:《C#基础知识加强

本文demo:https://github.com/zhaopeiym/BlogDemoCode

 

【推荐】

http://www.cnblogs.com/wisdomqq/archive/2012/03/29/2417723.html

 

【转】C#异步的世界【下】

可怜管理

有关新异步里面抛出特别的不利姿势。大家先来看下边一段代码:

private async void button8_Click(object sender, EventArgs e)
{
    Task<string> task = GetUlrStringErr(null);
    Thread.Sleep(1000);//一段逻辑。。。。
    textBox1.Text = await task;
}

public async Task<string> GetUlrStringErr(string url)
{
    if (string.IsNullOrWhiteSpace(url))
    {
        throw new Exception("url不能为空");
    }
    using (HttpClient http = new HttpClient())
    {
        return await http.GetStringAsync(url);
    }
}

调治实行试行流程:

亿万先生 17

在试行完118行的时候以致未有把分外抛出来?那不是逆天了啊。非得在守候await施行的时候才报错,显明119行的逻辑实施是未有怎么意义的。让大家把特别提前抛出:

亿万先生 18

领到叁个方法来做申明,那样就能够马上的抛出十分了。有心上人会说这么的太坑爹了吗,二个申明还必须其余写个点子。接下来大家提供多少个从未这么坑爹的格局:

亿万先生 19

在异步函数里面用匿名异步函数实行李包裹装,同样能够完毕即时验证。

以为也比不上前种艺术好些个少…只是能咋办呢。

新异步的优势

在此以前已经有了各类异步格局,为何还要引进和学习新的async\await异步呢?当然它一定是有其独特的优势。

我们分三个方面来解析:WinForm、WPF等单线程UI程序和Web后台服务程序。

对此Web后台服务程序

莫不对于后台程序的影响没有单线程程序那么直观,但其股票总市值也是可怜大的。且相当多人对新异步存在误会。

【误解】:新异步能够进级Web程序的性能。

【正解】:异步不会晋级单次央浼结果的时刻,然则足以拉长Web程序的吞吐量。

1、为何不会升高单次哀告结果的时光?

实际大家从上面示例代码(固然是UI程序的代码)也足以看到。

 亿万先生 20

2、为啥能够升高Web程序的吞吐量?

那什么样是吞吐量呢,也即是理当如此只好12个人同临时间做客的网址以往能够二十民用同期做客了。约等于常说的并发量。

或然用地点的代码来分解。[代码2]
阻塞了UI线程等待央浼结果,所以UI线程被占用,而[代码3]行使了新的线程须求,所以UI线程未有被占用,而得以持续响应UI分界面。

那难点来了,大家的Web程序原始正是多线程的,且web线程都以跑的线程池线程(使用线程池线程是为了防止不断开创、销毁线程所导致的能源资金财产浪费),而线程池线程可选拔线程数量是一定的,就算能够安装,但它照旧会在自然则然范围内。如此一来,我们web线程是可贵的(物以稀为贵),不能滥用。用完了,那么别的用户诉求的时候就不或许管理直接503了。

这怎么算是滥用呢?举例:文件读取、U大切诺基L要求、数据库访谈等IO央浼。如若用web线程来做那个耗费时间的IO操作那么就能够卡住web线程,而web线程阻塞得多了web线程池线程就远远不够用了。也就到达了web程序最大访谈数。

那时大家的新异步破土而出,解放了那么些原来管理IO央求而围堵的web线程(想偷懒?没门,干活了。)。通过异步情势利用相对廉价的线程(非web线程池线程)来拍卖IO操作,那样web线程池线程就足以解放出来管理越多的伸手了。

不信?上面大家来测验下:

【测验步骤】:

1、新建多少个web api项目 

2、新建三个多少访谈类,分别提供联合、异步方法(在章程逻辑实施前后读取时间、线程id、web线程池线程使用数)

public class GetDataHelper
{
    /// <summary>
    /// 同步方法获取数据
    /// </summary>
    /// <returns></returns>
    public string GetData()
    {
        var beginInfo = GetBeginThreadInfo();
        using (HttpClient http = new HttpClient())
        {
            http.GetStringAsync("https://github.com/").Wait();//注意:这里是同步阻塞
        }
        return beginInfo + GetEndThreadInfo();
    }

    /// <summary>
    /// 异步方法获取数据
    /// </summary>
    /// <returns></returns>
    public async Task<string> GetDataAsync()
    {
        var beginInfo = GetBeginThreadInfo();
        using (HttpClient http = new HttpClient())
        {
            await http.GetStringAsync("https://github.com/");//注意:这里是异步等待
        }
        return beginInfo + GetEndThreadInfo();
    }

    public string GetBeginThreadInfo()
    {
        int t1, t2, t3;
        ThreadPool.GetAvailableThreads(out t1, out t3);
        ThreadPool.GetMaxThreads(out t2, out t3);
        return string.Format("开始:{0:mm:ss,ffff} 线程Id:{1} Web线程数:{2}",
                                DateTime.Now,
                                Thread.CurrentThread.ManagedThreadId,                                  
                                t2 - t1);
    }

    public string GetEndThreadInfo()
    {
        int t1, t2, t3;
        ThreadPool.GetAvailableThreads(out t1, out t3);
        ThreadPool.GetMaxThreads(out t2, out t3);
        return string.Format(" 结束:{0:mm:ss,ffff} 线程Id:{1} Web线程数:{2}",
                                DateTime.Now,
                                Thread.CurrentThread.ManagedThreadId,
                                t2 - t1);
    }
}

3、新建二个web api调整器

[HttpGet]
public async Task<string> Get(string str)
{
    GetDataHelper sqlHelper = new GetDataHelper();
    switch (str)
    {
        case "异步处理"://
            return await sqlHelper.GetDataAsync();
        case "同步处理"://
            return sqlHelper.GetData();
    }
    return "参数不正确";           
}       

4、发表web
api程序,布署到地点iis(一起链接http://localhost:803/api/Home?str=同步处理 
异步链接http://localhost:803/api/Home?str=异步处理

5、接着上边的winform程序里面测验央求:(相同的时间提倡12个伏乞)

亿万先生 21亿万先生 22

private void button6_Click(object sender, EventArgs e)
{
    textBox1.Text = "";
    label1.Text = "";
    Task.Run(() =>
    {
        TestResultUrl("http://localhost:803/api/Home?str=同步处理");
    });
}

private void button5_Click(object sender, EventArgs e)
{
    textBox1.Text = "";
    label1.Text = "";
    Task.Run(() =>
    {
        TestResultUrl("http://localhost:803/api/Home?str=异步处理");
    });
}

public void TestResultUrl(string url)
{
    int resultEnd = 0;
    HttpClient http = new HttpClient();

    int number = 10;
    for (int i = 0; i < number; i++)
    {
        new Thread(async () =>
        {
            var resultStr = await http.GetStringAsync(url);
            label1.Invoke((Action)(() =>
            {
                textBox1.AppendText(resultStr.Replace(" ", "\r\t") + "\r\n");
                if (++resultEnd >= number)
                {
                    label1.Text = "全部执行完毕";
                }
            }));

        }).Start();
    }
}

View Code

6、重启iis,并用浏览器访谈贰次要必要的链接地址(预热)

7、运行winform程序,点击“访谈同步完结的Web”:

亿万先生 23

亿万先生 24

8、重复6,然后再一次起动winform程序点击“访谈异步达成的Web”

亿万先生 25

观望那些多少有啥感想?

数码和大家前边的【正解】完全契合。留神观看,每一个单次需要用时基本上相差相当小。
不过步骤7″同步达成”最高投入web线程数是10,而步骤8“异步达成”最高投入web线程数是3。

相当于说“异步完毕”使用越来越少的web线程达成了一样的乞请数量,如此一来大家就有越多剩余的web线程去管理更加的多用户发起的伏乞。

继之我们还开掘一块达成恳求前后的线程ID是大同小异的,而异步完结上下线程ID不必然一致。再一次表达实施await异步前释放了主线程。

【结论】:

  • 应用新异步能够进级Web服务程序的吞吐量
  • 对于客户端的话,web服务的异步并不会增加客户端的单次访问速度。
  • 举办新异步前会放出web线程,而等待异步实施到位后又回到了web线程上。进而抓实web线程的利用率。

【图解】:

亿万先生 26

 

对于WinForm、WPF等单线程UI程序

代码1(旧异步)

private void button1_Click(object sender, EventArgs e)
{
    var request = WebRequest.Create("https://github.com/");
    request.BeginGetResponse(new AsyncCallback(t =>
    {
        //(1)处理请求结果的逻辑必须写这里
        label1.Invoke((Action)(() => { label1.Text = "[旧异步]执行完毕!"; }));//(2)这里跨线程访问UI需要做处理      
    }), null);
}

代码2(同步)

private void button3_Click(object sender, EventArgs e)
{
    HttpClient http = new HttpClient();
    var htmlStr = http.GetStringAsync("https://github.com/").Result;
    //(1)处理请求结果的逻辑可以写这里
    label1.Text = "[同步]执行完毕!";//(2)不在需要做跨线程UI处理了
}

代码3(新异步)

 private async void button2_Click(object sender, EventArgs e)
 {
     HttpClient http = new HttpClient();
     var htmlStr = await http.GetStringAsync("https://github.com/");
     //(1)处理请求结果的逻辑可以写这里
     label1.Text = "[新异步]执行完毕!";//(2)不在需要做跨线程UI处理了
 }

新异步的优势:

  • 尚无了烦人的回调解和处理理
  • 不会像一道代码同样阻塞UI分界面(形成假死)
  • 不在像旧异步管理后访谈UI不在必要做跨线程处理
  • 像使用同步代码同样使用异步(超清晰的逻辑)

 是的,说得再多还不比看看实效图来得实在:(新旧异步UI线程未有阻塞,同步阻塞了UI线程)

亿万先生 27

【思量】:旧的异步格局是翻开了八个新的线程去施行,不会阻塞UI线程。那一点很好驾驭。可是,新的异步看上去和联合差别不大,为什么也不会阻塞分界面呢?

【原因】:新异步,在实行await表明式前都以选拔UI线程,await表明式后会启用新的线程去实施异步,直到异步实行到位并回到结果,然后再重回UI线程(听别人说使用了SynchronizationContext;k(SolutionItemsProject);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5.2);k(DevLang-csharp)&rd=true))。所以,await是未曾阻塞UI线程的,也就不会促成分界面包车型的士假死。

【注意】:大家在演示同步代码的时候使用了Result。然,在UI单线程程序中应用Result来使异步代码当一只代码应用是一件很凶险的事(起码对于不太精晓新异步的同班来说是那般)。至于实际原因稍候再分析(哎哎,别跑啊)。

接上篇:《C#异步的社会风气【上】

上篇主要分析了async\await以前的片段异步格局,今日说异步的重大是指C#5的async\await异步。在此为了有助于的抒发,大家称async\await从前的异步为“旧异步”,async\await为“新异步”。

异步的达成

上面简单分析了新异步本事和总体性。接下来让我们继续揭秘异步的实质,神秘的外衣上面终究是怎么落到实处的。

先是大家编辑二个用来反编写翻译的示范:

class MyAsyncTest
{
    public async Task<string> GetUrlStringAsync(HttpClient http, string url, int time)
    {
        await Task.Delay(time);
        return await http.GetStringAsync(url);
    }
}

反编写翻译代码:

点击看大图

为了方便阅读,大家把编写翻译器自动命名的体系重命名。

 GetUrlStringAsync 方法成为了这么颜值:

public Task<string> GetUrlStringAsync(HttpClient http, string url, int time)
{
    GetUrlStringAsyncdStateMachine stateMachine = new GetUrlStringAsyncdStateMachine()
    {
        _this = this,
        http = http,
        url = url,
        time = time,
        _builder = AsyncTaskMethodBuilder<string>.Create(),
        _state = -1
    };
    stateMachine._builder.Start(ref stateMachine);
    return stateMachine._builder.Task;
}

方法签名完全一致,只是个中的剧情形成了贰个气象机 GetUrlStringAsyncdStateMachine
 的调用。此状态机正是编写翻译器自动创设的。上边来探视神秘的状态机是如何鬼:

private sealed class GetUrlStringAsyncdStateMachine : IAsyncStateMachine
{
    public int _state;
    public MyAsyncTest _this;
    private string _str1;
    public AsyncTaskMethodBuilder<string> _builder;
    private TaskAwaiter taskAwaiter1;
    private TaskAwaiter<string> taskAwaiter2;

    //异步方法的三个形参都到这里来了
    public HttpClient http;
    public int time;
    public string url;

    private void MoveNext()
    {
        string str;
        int num = this._state;
        try
        {
            TaskAwaiter awaiter;
            MyAsyncTest.GetUrlStringAsyncdStateMachine d__;
            string str2;
            switch (num)
            {
                case 0:
                    break;

                case 1:
                    goto Label_00CD;

                default:
                    //这里是异步方法 await Task.Delay(time);的具体实现
                    awaiter = Task.Delay(this.time).GetAwaiter();
                    if (awaiter.IsCompleted)
                    {
                        goto Label_0077;
                    }
                    this._state = num = 0;
                    this.taskAwaiter1 = awaiter;
                    d__ = this;
                    this._builder.AwaitUnsafeOnCompleted<TaskAwaiter, MyAsyncTest.GetUrlStringAsyncdStateMachine>(ref awaiter, ref d__);
                    return;
            }
            awaiter = this.taskAwaiter1;
            this.taskAwaiter1 = new TaskAwaiter();
            this._state = num = -1;
        Label_0077:
            awaiter.GetResult();
            awaiter = new TaskAwaiter();
            //这里是异步方法await http.GetStringAsync(url);的具体实现
            TaskAwaiter<string> awaiter2 = this.http.GetStringAsync(this.url).GetAwaiter();
            if (awaiter2.IsCompleted)
            {
                goto Label_00EA;
            }
            this._state = num = 1;
            this.taskAwaiter2 = awaiter2;
            d__ = this;
            this._builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, MyAsyncTest.GetUrlStringAsyncdStateMachine>(ref awaiter2, ref d__);
            return;
        Label_00CD:
            awaiter2 = this.taskAwaiter2;
            this.taskAwaiter2 = new TaskAwaiter<string>();
            this._state = num = -1;
        Label_00EA:
            str2 = awaiter2.GetResult();
            awaiter2 = new TaskAwaiter<string>();
            this._str1 = str2;
            str = this._str1;
        }
        catch (Exception exception)
        {
            this._state = -2;
            this._builder.SetException(exception);
            return;
        }
        this._state = -2;
        this._builder.SetResult(str);
    }

    [DebuggerHidden]
    private void SetStateMachine(IAsyncStateMachine stateMachine)
    {
    }

}

显著几个异步等待奉行的时候正是在不停调用状态机中的MoveNext()方法。经验来至大家事先分析过的IEumerable,不过后天的这一个肯定复杂度要超越之前的异常。推测是那般,大家照旧来注解下实际:

在开场方法 GetUrlStringAsync 第贰遍运转状态机 stateMachine._builder.Start(ref stateMachine); 

亿万先生 28

 确实是调用了 MoveNext 。因为_state的先导值是-1,所以进行到了上面包车型地铁地方:

亿万先生 29

绕了一圈又赶回了 MoveNext 。因此,大家得以现象成四个异步调用正是在持续试行MoveNext直到停止。

说了这么久有哪些意思啊,仿佛忘记了小编们的目标是要因而事先编写的测验代码来剖析异步的执行逻辑的。

再次贴出在此之前的测验代码,以防忘记了。

亿万先生 30

反编写翻译后代码实行逻辑图:

亿万先生 31

理之当然那只是也许极大的实施流程,但也会有 awaiter.伊斯科mpleted 为 true 的气象。其余也许的留着我们本身去商讨吧。 

 

正文已联合签名至索引目录:《C#基础知识加强

本文demo:https://github.com/zhaopeiym/BlogDemoCode

 

【推荐】

http://www.cnblogs.com/wisdomqq/archive/2012/03/29/2417723.html

 

新异步的优势

在此以前已经有了各个异步形式,为何还要引入和学习新的async\await异步呢?当然它鲜明是有其特有的优势。

大家分多个地方来分析:WinForm、WPF等单线程UI程序和Web后台服务程序。

对于WinForm、WPF等单线程UI程序

代码1(旧异步)

private void button1_Click(object sender, EventArgs e)
{
    var request = WebRequest.Create("https://github.com/");
    request.BeginGetResponse(new AsyncCallback(t =>
    {
        //(1)处理请求结果的逻辑必须写这里
        label1.Invoke((Action)(() => { label1.Text = "[旧异步]执行完毕!"; }));//(2)这里跨线程访问UI需要做处理      
    }), null);
}

代码2(同步)

private void button3_Click(object sender, EventArgs e)
{
    HttpClient http = new HttpClient();
    var htmlStr = http.GetStringAsync("https://github.com/").Result;
    //(1)处理请求结果的逻辑可以写这里
    label1.Text = "[同步]执行完毕!";//(2)不在需要做跨线程UI处理了
}

代码3(新异步)

 private async void button2_Click(object sender, EventArgs e)
 {
     HttpClient http = new HttpClient();
     var htmlStr = await http.GetStringAsync("https://github.com/");
     //(1)处理请求结果的逻辑可以写这里
     label1.Text = "[新异步]执行完毕!";//(2)不在需要做跨线程UI处理了
 }

新异步的优势:

  • 尚未了烦人的回调解和管理理
  • 不会像一块代码同样阻塞UI分界面(变成假死)
  • 不在像旧异步管理后访谈UI不在须要做跨线程处理
  • 像使用同步代码同样使用异步(超清晰的逻辑)

 是的,说得再多还不及看看实效图来得实际:(新旧异步UI线程未有阻塞,同步阻塞了UI线程)

亿万先生 32

【思索】:旧的异步格局是翻开了四个新的线程去实施,不会阻塞UI线程。那点很好领会。可是,新的异步看上去和联合差距比一点都不大,为啥也不会阻塞分界面呢?

【原因】:新异步,在奉行await表明式前都以运用UI线程,await表达式后会启用新的线程去实践异步,直到异步推行到位并赶回结果,然后再再次回到UI线程(听大人讲使用了SynchronizationContext;k(SolutionItemsProject);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5.2);k(DevLang-csharp)&rd=true))。所以,await是不曾阻塞UI线程的,也就不会促成界面的假死。

【注意】:我们在示范同步代码的时候利用了Result。然,在UI单线程程序中应用Result来使异步代码当一只代码应用是一件很凶险的事(起码对于不太明白新异步的同窗来说是那样)。至于实际原因稍候再剖析(哎哎,别跑啊)。

相关文章