C萼異歩の世界(上)


前言
新しい段階のプログラマはasync、awaitに対して多く使うかもしれませんが、前の非同期についてはよく分かりません。本人はこのようなものですので、非同期の進化史を振り返ってみたいと思います。
本稿は主にasync非同期モードの前の非同期を振り返り、次の文章でasync非同期モードを重点的に分析する。
APM
APM非同期プログラミングモデル、Aynchronous Programming Model
早くC〓〓1の時APMがありました。あまり詳しくないですが、見たことがあります。これらのクラスはBeginXXXとEndXXXの方法で、BeginXXXの戻り値はIAsyncResultインターフェースです。
APMの例を正式に書く前に、まず同期コードを与えます。

//1、    
private void button1_Click(object sender, EventArgs e)
{          
    Debug.WriteLine("【Debug】  ID:" + Thread.CurrentThread.ManagedThreadId);

    var request = WebRequest.Create("https://github.com/");//         ,            
    request.GetResponse();//        

    Debug.WriteLine("【Debug】  ID:" + Thread.CurrentThread.ManagedThreadId);
    label1.Text = "    !";
}
【説明】非同期効果をより良く示すために、ここではwindformプログラムを使って例を示します。Windows formは常にUIスレッドレンダリングインターフェースが必要であり、UIスレッドによって占有されると「デッド」状態になります。
【効果図】

図を見て分かります
私たちは方法を実行している時にページに「仮死」が現れました。
印刷結果を見ましたが、メソッド呼び出し前と呼び出し後のスレッドIDは全部9です。つまり同じスレッドです。
対応する非同期方法を以下に示します。

private void button2_Click(object sender, EventArgs e)
{
    //1、APM       ,Asynchronous Programming Model
    //C#1[  IAsyncResult    BeginXXX EndXXX   ]             
    Debug.WriteLine("【Debug】   ID:" + Thread.CurrentThread.ManagedThreadId);

    var request = WebRequest.Create("https://github.com/");
    request.BeginGetResponse(new AsyncCallback(t =>//        
    {
        var response = request.EndGetResponse(t);
        var stream = response.GetResponseStream();//        

        using (StreamReader reader = new StreamReader(stream))
        {
            StringBuilder sb = new StringBuilder();
            while (!reader.EndOfStream)
            {
                var content = reader.ReadLine();
                sb.Append(content);
            }
            Debug.WriteLine("【Debug】" + sb.ToString().Trim().Substring(0, 100) + "...");//        100    
            Debug.WriteLine("【Debug】    ID:" + Thread.CurrentThread.ManagedThreadId);
            label1.Invoke((Action)(() => { label1.Text = "    !"; }));//       UI     
        }
    }), null);

    Debug.WriteLine("【Debug】   ID:" + Thread.CurrentThread.ManagedThreadId); 
}
【効果図】

図を見て分かります
  • 非同期化方法は、UIインターフェースカード死ではありません。
  • 非同期方法は、別のIDが12のスレッド
  • を起動する。
    上のコードの実行順序:

    前に言ったように、APMのBebinXXXはIAsyncResultインターフェースに戻らなければなりません。次に、IAsync Resultインターフェースを分析します。
    まず見ます

    確かに帰ってきたのはIAsyncResultインターフェースです。IAsync Resultはいったいどのように長いですか?

    思ったほど複雑ではないですね。このインターフェースを実現して、自分の非同期方法を示すことができますか?
    まずクラスMyWebRequestを決めて、IAsyncResultを継承します。
    
    public class MyWebRequest : IAsyncResult
    {
        public object AsyncState
        {
            get { throw new NotImplementedException(); }
        }
    
        public WaitHandle AsyncWaitHandle
        {
            get { throw new NotImplementedException(); }
        }
    
        public bool CompletedSynchronously
        {
            get { throw new NotImplementedException(); }
        }
    
        public bool IsCompleted
        {
            get { throw new NotImplementedException(); }
        }
    }
    これはきっと使えません。少なくとも保存コールバック関数の属性が必要です。以下はちょっと改造してみます。

    その後、APM非同期モデルをカスタマイズできます。
    
    public IAsyncResult MyBeginXX(AsyncCallback callback)
    {
        var asyncResult = new MyWebRequest(callback, null);
        var request = WebRequest.Create("https://github.com/");
        new Thread(() =>  //        
        {
            using (StreamReader sr = new StreamReader(request.GetResponse().GetResponseStream()))
            {
                var str = sr.ReadToEnd();
                asyncResult.SetComplete(str);//      
            }
    
        }).Start();
        return asyncResult;//    IAsyncResult
    }
    
    public string MyEndXX(IAsyncResult asyncResult)
    {
        MyWebRequest result = asyncResult as MyWebRequest;
        return result.Result;
    }
    以下のように呼び出します
    
    private void button4_Click(object sender, EventArgs e)
     {
         Debug.WriteLine("【Debug】   ID:" + Thread.CurrentThread.ManagedThreadId);
         MyBeginXX(new AsyncCallback(t =>
         {
             var result = MyEndXX(t);
             Debug.WriteLine("【Debug】" + result.Trim().Substring(0, 100) + "...");
             Debug.WriteLine("【Debug】    ID:" + Thread.CurrentThread.ManagedThreadId);
         }));
         Debug.WriteLine("【Debug】   ID:" + Thread.CurrentThread.ManagedThreadId);
     }
    効果図:

    私たちは自分が実現した効果は基本的にシステムが提供したものと同じです。
  • 非同期化方法は、UIインターフェースカード死ではありません。
  • 非同期方法は、別のIDが11のスレッド
  • を起動する。
    【まとめ】
    APM非同期モードとは、他のスレッドを有効にして、時間のかかるタスクを実行し、その後、コールバック関数によって後続の動作を実行することです。
    APMはまた、他の方法で値を取得することができる。
    
    while (!asyncResult.IsCompleted)//  ,         (    )
    {
        Thread.Sleep(100);
    }
    var stream2 = request.EndGetResponse(asyncResult).GetResponseStream();
    または
    
    asyncResult.AsyncWaitHandle.WaitOne();//    ,       (    )
    var stream2 = request.EndGetResponse(asyncResult).GetResponseStream();
    補足:普通の方法であれば、私達も非同期を依頼することができます。
    
    public void MyAction()
     {
         var func = new Func<string, string>(t =>
         {
             Thread.Sleep(2000);
             return "name:" + t + DateTime.Now.ToString();
         });
     
         var asyncResult = func.BeginInvoke("  ", t =>
         {
             string str = func.EndInvoke(t);
             Debug.WriteLine(str);
         }, null); 
     }
    EAP
    EAPはイベントの非同期モードに基づいて、Event-based Aynchronous Pattern
    このパターンはC〓2の時に付いてきます。
    まずEAPの例を見ます。
    
    private void button3_Click(object sender, EventArgs e)
     {            
         Debug.WriteLine("【Debug】   ID:" + Thread.CurrentThread.ManagedThreadId);
    
         BackgroundWorker worker = new BackgroundWorker();
         worker.DoWork += new DoWorkEventHandler((s1, s2) =>
         {
             Thread.Sleep(2000);
             Debug.WriteLine("【Debug】    ID:" + Thread.CurrentThread.ManagedThreadId);
         });//         
         worker.RunWorkerAsync(this);
         Debug.WriteLine("【Debug】   ID:" + Thread.CurrentThread.ManagedThreadId);
     }
    【効果図】(同様にUIインターフェースをブロックしない)

    【特徴】
  • は、イベントによってコールバック関数
  • を登録する。
  • は、XAsync方式により非同期呼び出しを実行する

  • 例は簡単ですが、APMモードと比べて、そんなにはっきりしていませんか?なぜこのように実現できますか?イベントの登録は何ですか?どうしてRunWorkerAryncを実行すると登録の関数がトリガされますか?
    自分はまた考えすぎたような気がします。
    私達は逆コンパイルしてソースを見てみます。

    ただ言いたいのですが、このように遊んだら面白いですか?
    TAP
    TAPはタスクの非同期モードに基づいて、Task-based Aynchronous Pattern
    今までのところ、上のAPM、EAP非同期モードはいいと思いますか?何か問題がないようです。もう少しよく考えてみますと、もし私たちが複数の非同期方法を持っているなら、先着順に実行しなければなりません。そして、(メインプロセスで)すべての戻り値を得る必要があります。
    まず三つの依頼を定義します。
    
    public Func<string, string> func1()
    {
        return new Func<string, string>(t =>
        {
            Thread.Sleep(2000);
            return "name:" + t;
        });
    }
    public Func<string, string> func2()
    {
        return new Func<string, string>(t =>
        {
            Thread.Sleep(2000);
            return "age:" + t;
        });
    }
    public Func<string, string> func3()
    {
        return new Func<string, string>(t =>
        {
            Thread.Sleep(2000);
            return "sex:" + t;
        });
    }
    そして、一定の順序で実行します。
    
    public void MyAction()
    {
        string str1 = string.Empty, str2 = string.Empty, str3 = string.Empty;
        IAsyncResult asyncResult1 = null, asyncResult2 = null, asyncResult3 = null;
        asyncResult1 = func1().BeginInvoke("  ", t =>
        {
            str1 = func1().EndInvoke(t);
            Debug.WriteLine("【Debug】    ID:" + Thread.CurrentThread.ManagedThreadId);
            asyncResult2 = func2().BeginInvoke("26", a =>
            {
                str2 = func2().EndInvoke(a);
                Debug.WriteLine("【Debug】    ID:" + Thread.CurrentThread.ManagedThreadId);
                asyncResult3 = func3().BeginInvoke(" ", s =>
                {
                    str3 = func3().EndInvoke(s);
                    Debug.WriteLine("【Debug】    ID:" + Thread.CurrentThread.ManagedThreadId);
                }, null);
            }, null);
        }, null);
    
        asyncResult1.AsyncWaitHandle.WaitOne();
        asyncResult2.AsyncWaitHandle.WaitOne();
        asyncResult3.AsyncWaitHandle.WaitOne();
        Debug.WriteLine(str1 + str2 + str3);
    }
    醜さ以外に、読みにくいことは何もないようです。でも本当にそうですか?

    async Result 2はnullですか
    これより分かるのは、第1の非同期操作が完了する前にasync Result 2を割り当てず、async Result 2が非同期待ちを実行した時に異常を報告することです。このように3つの非同期関数を制御できなくなり、一定の順序で実行してから戻り値を得ることができます。理論的には他の方法がありますが、コードがより複雑になります。
    はい、今は私たちのTAPが登場しました。

    Taskクラスの静的方法Runを呼び出すだけで、非同期を軽く使うことができます。
    戻り値を取得:
    
    var task1 = Task<string>.Run(() =>
    {
        Thread.Sleep(1500);
        Console.WriteLine("【Debug】task1   ID:" + Thread.CurrentThread.ManagedThreadId);
        return "  ";
    });
    //                
    task1.Wait();
    var value = task1.Result;//     
    Console.WriteLine("【Debug】    ID:" + Thread.CurrentThread.ManagedThreadId);
    上の複数の非同期を処理して順番に実行します。
    
    Console.WriteLine("【Debug】    ID:" + Thread.CurrentThread.ManagedThreadId);
    string str1 = string.Empty, str2 = string.Empty, str3 = string.Empty;
    var task1 = Task.Run(() =>
    {
        Thread.Sleep(500);
        str1 = "  :  ,";
        Console.WriteLine("【Debug】task1   ID:" + Thread.CurrentThread.ManagedThreadId);
    }).ContinueWith(t =>
    {
        Thread.Sleep(500);
        str2 = "  :25,";
        Console.WriteLine("【Debug】task2   ID:" + Thread.CurrentThread.ManagedThreadId);
    }).ContinueWith(t =>
    {
        Thread.Sleep(500);
        str3 = "  :  ";
        Console.WriteLine("【Debug】task3   ID:" + Thread.CurrentThread.ManagedThreadId);
    });
    
    Thread.Sleep(2500);//      
    
    task1.Wait();
    
    Debug.WriteLine(str1 + str2 + str3);
    Console.WriteLine("【Debug】    ID:" + Thread.CurrentThread.ManagedThreadId);
    [効果図]

    結果はすべて得られ、非同期的に順を追って実行されることを示した。また、コードの論理的な思考は非常に明確です。もしあなたが感じるのがそんなに大きくないならば、もしあなたの現象は100の非同期の方法ならば、非同期の順序で実行しますか?APMの非同期フィードバックを使って、それは少なくとも非同期的に100回ネストしなければなりません。そのコードの複雑さは推して知るべしだ。
    思考を伸ばす
  • WaitOne完成待ちの原理
  • 非同期はどうして性能を上げますか?
  • スレッドの使用数はCPUの使用率と必然的に関係がありますか?
    問題1:WaitOne完成待ちの原理
    その前に、まず、マルチスレッド信号制御AutoReetEventクラスを簡単に理解してみましょう。
    
    var _asyncWaitHandle = new AutoResetEvent(false);
    _asyncWaitHandle.WaitOne();
    このコードはWaitOneのところでずっと待っています。他のスレッドがある場合を除き、AutoReseetEventのset方法を実行します。
    
    var _asyncWaitHandle = new AutoResetEvent(false);
    _asyncWaitHandle.Set();
    _asyncWaitHandle.WaitOne();
    このようにWaitOneに行くと直接実行できます。待つものは何もない。
    今はAPM非同期プログラミングモデルのWaitOneに対して待っていますが、何を知っていますか?私たちは後でカスタマイズした非同期の方法を実現します。
    
    public class MyWebRequest : IAsyncResult
    {
        //      (  )
        private AsyncCallback _asyncCallback;
        private AutoResetEvent _asyncWaitHandle;
        public MyWebRequest(AsyncCallback asyncCallback, object state)
        {
            _asyncCallback = asyncCallback;
            _asyncWaitHandle = new AutoResetEvent(false);
        }
        //    
        public void SetComplete(string result)
        {
            Result = result;
            IsCompleted = true;
            _asyncWaitHandle.Set();
            if (_asyncCallback != null)
            {
                _asyncCallback(this);
            }
        }
        //       
        public string Result { get; set; }
        //         ,               。
        public object AsyncState
        {
            get { throw new NotImplementedException(); }
        }
        //               System.Threading.WaitHandle。
        public WaitHandle AsyncWaitHandle
        {
            //get { throw new NotImplementedException(); }
    
            get { return _asyncWaitHandle; }
        }
        //     ,              。
        public bool CompletedSynchronously
        {
            get { throw new NotImplementedException(); }
        }
        //     ,             。
        public bool IsCompleted
        {
            get;
            private set;
        }
    }
    赤いコードは追加の非同期待ちです。
    【実行手順】

    問題2:非同期はなぜ性能を向上させますか?
    同期コードのようなもの:
    
    Thread.Sleep(10000);//             
    Thread.Sleep(10000);//              
    このコードは20秒必要です。
    非同期の場合:
    
    var task = Task.Run(() =>
    {
        Thread.Sleep(10000);//             
    });
    Thread.Sleep(10000);//              
    task.Wait();
    これで10秒しかないです。これで10秒の節約になります。
    もしそうであれば、
    
    var task = Task.Run(() =>
    {
        Thread.Sleep(10000);//             
    }); 
    task.Wait();
    非同期実行中に時間がかからないコードでは、このような非同期は意味がありません。
    または:
    
    var task = Task.Run(() =>
    {
        Thread.Sleep(10000);//             
    }); 
    task.Wait();
    Thread.Sleep(10000);//              
    時間のかかるタスクを非同期で待っても、このようなコードは性能が向上しません。
    もう一つの状況があります。
    単一コアCPUで高密集演算を行うなら、非同期も意味がない(演算は非常にCPUを消費するため、ネットワーク要求はCPUを消費しない)
    問題3:スレッドの使用数とCPUの使用率は必然的に関係がありますか?
    答えはどうですか
    やはりシングル核を仮説にします。
    状況1:
    
    long num = 0;
    while (true)
    {
        num += new Random().Next(-100,100);
        //Thread.Sleep(100);
    }
    シングルコアでは、スレッドを起動するだけで、CPUをいっぱいにすることができます。


    8回起動して、8プロセスのCPUはほぼ満タンです。
    状況2:


    1000以上のスレッドで、CPUの使用率はなんと0です。これにより,スレッドの使用数とCPUの使用率には必然的な関係がないという結論を得た。
    とはいえ、不用意にスレッドを開くことはできない。なぜなら、
  • 新しいスレッドを開くプロセスは、比較的リソースが消費されます。しかし、スレッドを使用して、新しいスレッドを開くために消費されるリソースを低減する
  • マルチスレッドの切り替えにも時間がかかります。
  • スレッド毎に一定のメモリを占有してスレッドコンテキスト情報を保存します。
  • 以上はC铉异歩の世界(上)の详细です。C菗异歩の世界に関する资料がもっと多いです。他の関连する文章に注目してください。