异步客户端服务设计模式

时间:2013-04-10 16:28:27

标签: c# service .net-4.0 tcpclient

我正在尝试设计一个邮件客户端服务,它允许使用TcpClient类连接和发送/接收命令。我还希望在回调每个函数时自动调用调用线程,这样调用者就不需要了。

我发现我需要为每个函数编写至少三到四倍的代码来实现它,而不是我要同步编写它。最大的问题是我必须为每个回调编写一个单独的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);
    }
}

1 个答案:

答案 0 :(得分:0)

这是我迄今为止成功使用的代码的一般格式。

它不会阻塞调用线程(UI),它总是在调用线程上调用它的回调,但它在后台线程中执行每个任务的大部分工作。

如果线程需要对共享资源(例如开放网络/ SSL流)的独占访问权限,那么您可以使用锁定器来封送此资源的使用,因此一次只有一个线程使用它。

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