Visual C# で非同期処理(async/await)を複数動かすとUIがフリーズする


初めまして、現在C#にて非同期処理を使ったプログラムを作成しています。

経緯として、ある外部機器のメモリ空間に、一定の値を連続して書き込むものを作りたいと考えました。  
その項目にチェックボックスを入れると、チェック中はasync/awaitを利用し、非同期処理で書き込む処理を続ける、というものを作りました。

ですが、同じ構造のものを複数作り、3つほどチェックを入れると、UIがフリーズしてしまいます。  
メモリへの書き込みは出来ているため、アプリケーションは落ちていないようです。

UIが固まるのはどういった原因が考えられるのでしょうか。

[code]

        #region Always UAV
    
        private async void kpUAV_CheckedChanged( object sender , EventArgs e )
        {
            if ( kpUAV.Checked )
            {
                while ( kpUAV.Checked )
                {
                    ulong targetClient = 0x21A8164 + (0x17010 * (ulong)kpSelectClient.Value ) ;
                    PS4.WriteMemory( this.processID , targetClient + 0x714      , new byte[]{ 0x01 } ) ; // UAV1
                    PS4.WriteMemory( this.processID , targetClient + 0xBC74     , new byte[]{ 0x01 } ) ; // UAV2
                    PS4.WriteMemory( this.processID , targetClient + 0x16F9C    , new byte[]{ 0x01 } ) ; // UAV3
                    await Task.Run( ( ) => { Thread.Sleep( 150 ) ; } ) ;
                }
            }
            else
            {
                ulong targetClient = 0x21A8164 + (0x17010 * (ulong)kpSelectClient.Value ) ;
                PS4.WriteMemory( this.processID , targetClient + 0x714      , new byte[]{ 0x00 } ) ; // UAV1
                PS4.WriteMemory( this.processID , targetClient + 0xBC74     , new byte[]{ 0x00 } ) ; // UAV2
                PS4.WriteMemory( this.processID , targetClient + 0x16F9C    , new byte[]{ 0x00 } ) ; // UAV3
            }
        }
    
        #endregion
    
        #region Always VSAT
    
        private async void kpVSAT_CheckedChanged( object sender , EventArgs e )
        {
            if ( kpVSAT.Checked )
            {
                while ( kpVSAT.Checked )
                {
                    ulong targetClient = 0x21A8164 + (0x17010 * (ulong)kpSelectClient.Value ) ;
                    PS4.WriteMemory( this.processID , targetClient + 0x718      , new byte[]{ 0x01 } ) ; // VSAT1
                    PS4.WriteMemory( this.processID , targetClient + 0xBC78     , new byte[]{ 0x01 } ) ; // VSAT2
                    PS4.WriteMemory( this.processID , targetClient + 0x16FA0    , new byte[]{ 0x01 } ) ; // VSAT3
                    await Task.Run( ( ) => { Thread.Sleep( 150 ) ; } ) ;
                }
            }
            else
            {
                ulong targetClient = 0x21A8164 + (0x17010 * (ulong)kpSelectClient.Value ) ;
                PS4.WriteMemory( this.processID , targetClient + 0x718      , new byte[]{ 0x00 } ) ; // VSAT1
                PS4.WriteMemory( this.processID , targetClient + 0xBC78     , new byte[]{ 0x00 } ) ; // VSAT2
                PS4.WriteMemory( this.processID , targetClient + 0x16FA0    , new byte[]{ 0x00 } ) ; // VSAT3
            }
        }
    
        #endregion
    
        #region Laser locator
    
        private async void kpLaserLocator_CheckedChanged( object sender , EventArgs e )
        {
            if (  kpLaserLocator.Checked )
            {
                while (  kpLaserLocator.Checked )
                {
                    ulong targetClient = 0x21A8164 + (0x17010 * (ulong)kpSelectClient.Value ) ;
                    PS4.WriteMemory( this.processID , targetClient + 0x782      , new byte[]{ 0x01 } ) ; // Point laser1
                    PS4.WriteMemory( this.processID , targetClient + 0xBCE2     , new byte[]{ 0x01 } ) ; // Point laser2
                    PS4.WriteMemory( this.processID , targetClient + 0x17740    , new byte[]{ 0x01 } ) ; // Point laser3
                    await Task.Run( ( ) => { Thread.Sleep( 150 ) ; } ) ;
                }
            }
            else
            {
                ulong targetClient = 0x21A8164 + (0x17010 * (ulong)kpSelectClient.Value ) ;
                PS4.WriteMemory( this.processID , targetClient + 0x782      , new byte[]{ 0x00 } ) ; // Point laser1
                PS4.WriteMemory( this.processID , targetClient + 0xBCE2     , new byte[]{ 0x00 } ) ; // Point laser2
                PS4.WriteMemory( this.processID , targetClient + 0x17740    , new byte[]{ 0x00 } ) ; // Point laser3
            }
        }
    
        #endregion
    
[/code]

[![左のチェックボックスを3つほどつけるとUIがフリーズします](https://i.stack.imgur.com/KgmSV.png)](https://i.stack.imgur.com/KgmSV.png)




Visual Studio 2017、.NET Framework 4.5のFormアプリケーションで検証してみました。

まず`PS4.WriteMemory`抜きで同様のコードを動かしてみた感じでは、チェックボックスを3つ入れても動作が重くなる現象は再現しませんでした。  
`async`、`await`、`Task`の使い方は問題ないようです。  
念の為`PS4.WriteMemory`が呼ばれている箇所で`Thread.CurrentThread.ManagedThreadId`を呼び出してスレッドIDも確認しましたが、すべて"1"だったのでメインスレッドでした。  
同一スレッド上の実行であれば、`PS4.WriteMemory`がスレッドセーフで無かったとしても問題ないでしょう。

考えられる可能性は、単純に`PS4.WriteMemory`の処理に時間がかかっていることが考えられます。  
コードではスリープしている時間は150msなので、`PS4.WriteMemory`×n個の処理が150msを超えるようであれば、処理時間が待機時間を上回るのでUIが応答しなくなると思われます。  
試しに`PS4.WriteMemory`×3個分を`Thread.Sleep(70)`として配置すると、チェックボックスを3つ入れた段階でほぼ操作不能に陥りました。

手っ取り早い解決策は、`Task`内のスリープ時間を伸ばすことです。  
待機時間が処理時間よりもずっと大きければ、重いでしょうが操作は可能です。  
抜本的な解決策としては、`PS4.WriteMemory`の処理を`System.Threading.Timer`内で行うことが考えれます。  
チェックボックスごとに`Timer`を作り、`Timer`の開始と停止を制御するような形にします。  
別スレッドの処理となるため、排他制御が必要となります。  
またアプリケーションの終了時には、`Timer.Dispose()`を呼び出して解放してください。

