http://www.forfreezone.com

异步的世界

对此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线程)

图片 1

【考虑】:旧的异步方式是翻开了一个新的线程去实践,不会窒碍UI线程。那一点很好驾驭。然则,新的异步看上去和联合差距超级小,为啥也不会卡住分界面呢?

【原因】:新异步,在施行await表明式前都以使用UI线程,await表明式后会启用新的线程去执行异步,直到异步试行到位并赶回结果,然后再回来UI线程(听他们讲使用了SynchronizationContext;k(SolutionItemsProject卡塔尔(قطر‎;k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5.2卡塔尔;k(DevLang-csharp卡塔尔国&rd=true卡塔尔(英语:State of Qatar))。所以,await是从未堵塞UI线程的,也就不会招致分界面包车型地铁假死。

【注意】:大家在演示同步代码的时候利用了Result。然,在UI单线程程序中运用Result来使异步代码当一只代码应用是风华正茂件很危殆的事(起码对于不太明白新异步的同校来说是这般)。至于具体原因稍候再解析(哎哎,别跑啊)。

新异步的优势

以前已经有了两种异步格局,为啥还要引进和学习新的asyncawait异步呢?当然它自然是有其极度的优势。

我们分四个地点来深入分析:WinForm、WPF等单线程UI程序和Web后台服务程序。

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(" 的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卡塔尔(英语:State of Qatar);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#异步的世界【下】

老大管理

有关新异步里面抛出十一分的准确姿势。大家先来看上边大器晚成段代码:

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

调治将养推行施行流程:

图片 2

在进行完118行的时候依然从未把那几个抛出来?那不是逆天了吗。非得在伺机await试行的时候才报错,分明119行的逻辑推行是未曾什么意思的。让大家把十三分提前抛出:

图片 3

领到叁个措施来做验证,那样就会即时的抛出极度了。有朋友会说这么的太坑爹了吧,三个表达还必得其余写个艺术。接下来大家提供一个尚无如此坑爹的情势:

图片 4

在异步函数里面用无名异步函数进行包装,相仿能够兑现即时验证。

感觉也不如前种方法非常多少...但是能咋做吧。

上篇主要解析了asyncawait从前的有的异步情势,几方今说异步的机假设指C#5的asyncawait异步。在那为了方便的发挥,大家称asyncawait早先的异步为“旧异步”,asyncawait为“新异步”。

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后续操作没有须求复苏到主线程(无需保存线程上下文)。

图片 5

新异步的选择

一定要说新异步的行使太简单(如果仅仅只是说接纳)

艺术加上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;
 }

接受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); 

图片 6

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

图片 7

绕了黄金时代圈又赶回了 MoveNext 。因而,大家得以现象成多个异步调用正是在持续试行MoveNext直到甘休。

说了这么久有啥样意思啊,好似忘记了我们的目标是要因而事情未发生前编写的测量试验代码来深入分析异步的施行逻辑的。

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

图片 8

反编译后代码实践逻辑图:

图片 9

自然那只是可能非常的大的实行流程,但也可能有 awaiter.Iscompleted 为 true 的情形。其余也许的留着咱们温馨去雕饰吧。 

 

本文已一同至索引目录:《C#功底知识加强》

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

 

【推荐】

 

对于Web后台服务程序

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

【误解】:新异步可以荣升Web程序的品质。

【正解】:异步不会进级单次诉求结果的光阴,不过可以增加Web程序的吞吐量。

1、为何不会进级单次央浼结果的年月?

骨子里大家从地方示例代码(纵然是UI程序的代码)也足以看出。

 图片 10

2、为何可以增加Web程序的吞吐量?

那怎么样是吞吐量呢,也正是理所必然只好12人同偶尔候做客的网址今后能够八十私家同一时候做客了。也正是常说的并发量。

恐怕用地点的代码来分解。[代码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(协助举行链接:  异步链接:) 

5、接着上边包车型地铁winform程序里面测验乞求:(同期提倡10个央求卡塔尔(英语:State of Qatar)

图片 11图片 12

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(" ", "rt") + "rn");
                if (++resultEnd >= number)
                {
                    label1.Text = "全部执行完毕";
                }
            }));

        }).Start();
    }
}

View Code

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

7、运营winform程序,点击“访问同步落成的Web”:

图片 13

图片 14

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

图片 15

看看这几个数量有哪些感想?

数码和我们眼下的【正解】完全合乎。留心观看,每一种单次央浼用时基本上相差十分的小。 可是步骤7"同步完结"最高投入web线程数是10,而步骤8“异步完毕”最高投入web线程数是3。

也便是说“异步完毕”使用更加少的web线程完结了一直以来的伏乞数量,如此一来大家就有越来越多剩余的web线程去管理越来越多客商发起的伸手。

跟着大家还开掘一齐达成央浼前后的线程ID是平等的,而异步达成上下线程ID不自然一致。再一次验证试行await异步前释放了主线程。

【结论】:

  • 选取新异步可以荣升Web服务程序的吞吐量
  • 对于客户端的话,web服务的异步并不会增高客商端的单次访谈速度。
  • 实行新异步前会自由web线程,而等待异步施行到位后又赶回了web线程上。进而加强web线程的利用率。

【图解】:

图片 16

 

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

郑重声明:本文版权归澳门新葡8455最新网站所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。