public VoiceRecognizerWithAmiVoiceCloud(string wsUri, string appKey)
        {
            var uri = new Uri(wsUri);

            if (uri.Scheme != "ws" && uri.Scheme != "wss")
            {
                throw new ArgumentException("Invalid scheme");
            }
            connectionUri = uri;
            this.appKey   = appKey;
            this.engine   = "-a-general";

            wsAmiVoice   = new ClientWebSocket();
            sendQueue    = new ConcurrentQueue <byte[]>();
            receiveQueue = new ConcurrentQueue <string>();

            connectionParameter = new Dictionary <string, string>();

            ProvidingState   = ProvidingStateType.Initialized;
            DetectingState   = DetectingStateType.NotDetecting;
            RecognizingState = RecognizingStateType.NotRecognizing;
        }
        protected async Task MessageLoop(CancellationToken ct)
        {
            ProvidingState = ProvidingStateType.Initialized;

            try
            {
                Func <Task <bool> > connectAction = async() =>
                {
                    var connectionResult = await Connect(ct);

                    if (connectionResult.isSuccess == false)
                    {
                        LastErrorString = connectionResult.message;
                        ErrorOccured?.Invoke(this, LastErrorString);
                        return(false);
                    }
                    Trace?.Invoke(this, "Connection complete.");
                    return(true);
                };

                sendQueue.Clear();

                CancellationTokenSource receiveTokenSource = new CancellationTokenSource();
                CancellationToken       receiveToken       = receiveTokenSource.Token;
                Task?receiveTask = null;

                char[] charsToTrim = { ' ', '\x00' };
                while (!ct.IsCancellationRequested)
                {
                    if (ProvidingState == ProvidingStateType.Initialized || (receiveTask != null && receiveTask.Status == TaskStatus.RanToCompletion))
                    {
                        Trace?.Invoke(this, "Try to connect.");
                        if (receiveTask != null && receiveTask.Status == TaskStatus.Running)
                        {
                            receiveTokenSource?.Cancel();
                            await receiveTask;
                        }

                        var connectResult = await connectAction();

                        if (!connectResult)
                        {
                            break;
                        }

                        receiveTask?.Dispose();
                        receiveTokenSource = new CancellationTokenSource();
                        receiveToken       = receiveTokenSource.Token;
                        receiveTask        = Task.Run(() => ReceiveLoop(receiveToken), receiveToken);
                    }

                    string receiveData;
                    if (receiveQueue.TryDequeue(out receiveData))
                    {
                        receiveData = receiveData.Trim(charsToTrim);
                        //Debug.WriteLine(String.Format("Recieve: {0}", receiveData));

                        // セッションタイムアウト等による切断
                        // 実際は強制的に切断されていたりするのでここを通らなかったりする
                        if (receiveData.StartsWith("p") && receiveData.Length > 3)
                        {
                            Debug.WriteLine("Timeout occured.");
                            Trace?.Invoke(this, receiveData.Substring(1).Trim());
                            RecognizingState = RecognizingStateType.NotRecognizing;
                            DetectingState   = DetectingStateType.NotDetecting;
                            ProvidingState   = ProvidingStateType.Initialized;
                            continue;
                        }

                        // エラー処理
                        if ((receiveData.StartsWith("e") && receiveData.Length > 3))
                        {
                            LastErrorString = receiveData;
                            ErrorOccured?.Invoke(this, receiveData.Substring(1));
                            ProvidingState   = ProvidingStateType.Error;
                            RecognizingState = RecognizingStateType.NotRecognizing;
                            DetectingState   = DetectingStateType.NotDetecting;
                            break;
                        }

                        // 発話区間開始
                        if (receiveData.StartsWith("S"))
                        {
                            uint startMiliSec;
                            if (uint.TryParse(receiveData.Substring(2), out startMiliSec))
                            {
                                //Debug.WriteLine(String.Format("S: {0}", receiveData));
                                VoiceStart?.Invoke(this, startMiliSec);
                            }
                            DetectingState = DetectingStateType.Detecting;
                        }

                        // 発話区間終了
                        if (receiveData.StartsWith("E"))
                        {
                            uint endMiliSec;
                            if (uint.TryParse(receiveData.Substring(2), out endMiliSec))
                            {
                                //Debug.WriteLine(String.Format("E: {0}", receiveData));
                                VoiceEnd?.Invoke(this, endMiliSec);
                            }
                            DetectingState = DetectingStateType.NotDetecting;
                        }

                        // 認識処理開始
                        if (receiveData.StartsWith("C"))
                        {
                            RecognizingState = RecognizingStateType.Recognizing;
                            RecognizeStarting?.Invoke(this, true);
                        }

                        // 認識処理返却
                        if (receiveData.StartsWith("U") || receiveData.StartsWith("A"))
                        {
                            try
                            {
                                //Debug.WriteLine(receiveData.Substring(2).Trim());
                                var result = JsonSerializer.Deserialize <SpeechRecognitionEventArgs>(receiveData.Substring(2), jsonSerializerOptions);
                                if (receiveData.StartsWith("U"))
                                {
                                    // 認識途中
                                    Recognizing?.Invoke(this, result);
                                }
                                else if (receiveData.StartsWith("A"))
                                {
                                    // 認識終了
                                    Recognized?.Invoke(this, result);
                                    RecognizingState = RecognizingStateType.NotRecognizing;
                                }
                            }
                            catch (JsonException ex)
                            {
                                Debug.WriteLine(ex.Message);
                            }
                        }
                    }

                    // 受信が異常終了していないか確認
                    if (receiveTask != null && receiveTask.Status == TaskStatus.Faulted)
                    {
                        Debug.WriteLine(String.Format("Loop:ReceiveWSException: {0}", receiveTask.Exception.InnerException.Message));
                        ErrorOccured?.Invoke(this, String.Format("ReceiveTaskException"));
                        break;
                    }

                    // 音声データを送る
                    byte[] sendData;
                    if (wsAmiVoice.State == WebSocketState.Open && sendQueue.TryDequeue(out sendData))
                    {
                        sendData = prefixC.Concat(sendData).ToArray();
                        if (sendData.Length == 0)
                        {
                            continue;
                        }
                        try
                        {
                            await wsAmiVoice.SendAsync(sendData, WebSocketMessageType.Binary, true, CancellationToken.None);
                        }
                        catch (Exception ex) when(ex is WebSocketException || ex is IOException)
                        {
                            var sendErrString = String.Format("Send:WebSocketException: {0}", ex.Message);

                            Trace?.Invoke(this, sendErrString);
                        }
                    }
                }

                // 終了処理
                byte[] endArray = new byte[] { (byte)CommandType.End };
                await wsAmiVoice.SendAsync(endArray, WebSocketMessageType.Text, true, CancellationToken.None);

                string disconnectionStr = "";
                while (wsAmiVoice.State == WebSocketState.Open && receiveTask != null && receiveTask.Status == TaskStatus.Running)
                {
                    if (!receiveQueue.TryDequeue(out disconnectionStr))
                    {
                        continue;
                    }

                    if (disconnectionStr.StartsWith("e") && disconnectionStr.Length == 1)
                    {
                        receiveTokenSource?.Cancel();
                        receiveTask.Wait(1000);
                        ProvidingState   = ProvidingStateType.Initialized;
                        RecognizingState = RecognizingStateType.NotRecognizing;
                        DetectingState   = DetectingStateType.NotDetecting;
                        RecognizeStopped?.Invoke(this, true);
                    }
                    else if (disconnectionStr.StartsWith("e"))
                    {
                        RecognizingState = RecognizingStateType.NotRecognizing;
                        DetectingState   = DetectingStateType.NotDetecting;
                        ProvidingState   = ProvidingStateType.Error;
                        LastErrorString  = disconnectionStr.Substring(2);
                        ErrorOccured?.Invoke(this, LastErrorString);
                    }
                }

                if (receiveTokenSource != null && !receiveTokenSource.IsCancellationRequested)
                {
                    receiveTokenSource.Cancel();
                    if (receiveTask != null && receiveTask.Status == TaskStatus.Running)
                    {
                        receiveTask.Wait(3000);
                    }
                }
            }
            catch (WebSocketException ex)
            {
                ErrorOccured?.Invoke(this, String.Format("Loop:WebSocketException: {0}", ex.Message));
            }
            finally
            {
                if (wsAmiVoice.State == WebSocketState.Open)
                {
                    try
                    {
                        await wsAmiVoice.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
                    }
                    catch (WebSocketException ex)
                    {
                        Debug.WriteLine(String.Format("Close:WebSocketException: {0} - {1}", ex.Message, wsAmiVoice.State.ToString()));
                    }
                }
            }

            Trace?.Invoke(this, "Disconnected.");
        }