0

TcpClient クラスを使用してコマンドの接続と送受信を可能にするメール クライアント サービスを設計しようとしています。また、各関数のコールバック時に呼び出し元のスレッドを自動的に呼び出して、呼び出し元が必要ないようにしたいと考えています。

これを実装するには、関数ごとに少なくとも 3 倍から 4 倍多くのコードを記述する必要があることがわかっています。最大の問題は、コールバックごとに個別の try/catch を作成する必要があることです。

Connect 関数を投稿します。うまくいけば、誰かがより良い方法を提案できます。

public virtual void Connect(Action<Exception> callback, string hostname, int port, bool ssl, RemoteCertificateValidationCallback validateCertificate)
{
    if (State != ConnectionState.Disconnected)
        throw new InvalidOperationException(AlreadyConnectedString);

    Host = hostname;
    Port = port;
    Ssl = ssl;

    var context = SynchronizationContext.Current;

    // Callback on the caller's thread
    Action<Exception> onCallback = (Exception ex) =>
        {
            context.Post(_ =>
                {
                    callback(ex);
                }, null);
        };

    // Called on any raised exceptions
    Action<Exception> onFail = (Exception ex) =>
        {
            State = ConnectionState.Disconnected;
            Cleanup();
            onCallback(ex);
        };

    // Check for a valid response
    Action<string, Exception> onConnectResponse = (string response, Exception ex) =>
        {
            if (ex != null)
                onFail(ex);

            try
            {
                OnConnected(response);
                onCallback(ex);
            }
            catch (Exception responseException)
            {
                onFail(responseException);
            }
        };

    // Callback after SSL authentication
    AsyncCallback onAuthenticated = (IAsyncResult result) =>
        {
            try
            {
                var sslStream = (SslStream)result.AsyncState;
                sslStream.EndAuthenticateAsClient(result);

                State = ConnectionState.Authorization;

                GetResponse(onConnectResponse);
            }
            catch (Exception authenticateException)
            {
                onFail(authenticateException);
            }
        };

    // Callback after TcpClient connect
    AsyncCallback onConnect = (IAsyncResult result) =>
        {
            try
            {
                _Connection.EndConnect(result);

                _Stream = _Connection.GetStream();

                if (ssl)
                {
                    SslStream sslStream;

                    if (validateCertificate != null)
                        sslStream = new SslStream(_Stream, false, validateCertificate);
                    else
                        sslStream = new SslStream(_Stream, false);

                    _Stream = sslStream;

                    sslStream.BeginAuthenticateAsClient(hostname, onAuthenticated, sslStream);
                }
                else
                {
                    State = ConnectionState.Authorization;

                    GetResponse(onConnectResponse);
                }
            }
            catch (Exception connectException)
            {
                onFail(connectException);
            }
        };

    try
    {
        _Connection = new TcpClient();
        _Connection.BeginConnect(hostname, port, onConnect, null);
    }
    catch (Exception ex)
    {
        onFail(ex);
    }
}
4

1 に答える 1

0

以下は、これまでに使用して成功したコードの一般的な形式です。

呼び出し元のスレッド (UI) をブロックせず、常に呼び出し元のスレッドでコールバックを呼び出しますが、バックグラウンド スレッドで各タスクの作業の大部分を実行します。

スレッドが共有リソース (オープン ネットワーク/SSL ストリームなど) への排他的アクセスを必要とする場合は、ロッカーを使用してこのリソースの使用をマーシャリングし、一度に 1 つのスレッドだけがそれを使用するようにすることができます。

public void Connect(Action<Exception> callback, string hostname, int port, bool ssl, RemoteCertificateValidationCallback validateCertificate)
{
    if (State != ConnectionState.Disconnected)
        throw new InvalidOperationException(AlreadyConnectedString);

    Host = hostname;
    Port = port;
    Ssl = ssl;
    State = ConnectionState.Connecting;

    var callingThread = TaskScheduler.FromCurrentSynchronizationContext();

    Action connectAction = () =>
    {
        // Connect asynchronously in order to specify a timeout
        TcpClient connection = new TcpClient();
        connection.SendTimeout = SendTimeout;
        connection.ReceiveTimeout = ReadTimeout;

        IAsyncResult ar = connection.BeginConnect(hostname, port, null, null);
        WaitHandle waitHandle = ar.AsyncWaitHandle;

        try
        {
            if (!ar.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(ConnectTimeout), false))
                throw new TimeoutException();

            connection.EndConnect(ar);
        }
        finally
        {
            waitHandle.Close();
        }

        Stream stream = connection.GetStream();

        if (ssl)
        {
            SslStream sslStream;

            if (validateCertificate != null)
                sslStream = new SslStream(stream, false, validateCertificate);
            else
                sslStream = new SslStream(stream, false);

            sslStream.AuthenticateAsClient(hostname);

            stream = sslStream;
        }

        lock (_locker) // Perform thread unsafe operations here
        {
            _connection = connection;
            _stream = stream;
        }

        OnConnected(GetResponse());
    };

    Action<Task> completeAction = (Task task) =>
    {
        Exception ex = (task.Exception != null) ? task.Exception.InnerException : task.Exception;

        if (task.Exception != null)
        {
            Cleanup();
        }
        else
        {
            State = ConnectionState.Authorization;
        }

        if (callback != null)
            callback(ex);
    };

    Task.Factory.StartNew(connectAction, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default)
                .ContinueWith(completeAction, callingThread);
}
于 2013-04-22T21:15:11.463 に答える