异步客户端服务设计模式

本文关键字:设计模式 服务 客户端 异步 | 更新日期: 2023-09-27 18:08:24

我试图设计一个邮件客户端服务,它允许使用TcpClient类连接和发送/接收命令。我还想在每个函数的回调时自动调用调用线程,以便调用者不需要。

我发现,为了实现这一点,我需要为每个函数编写至少三到四倍的代码,而不是同步编写。最大的问题是我必须为每个回调编写单独的try/catch。

我会张贴我的连接功能,希望有人能建议一个更好的方法:

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

异步客户端服务设计模式

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

它不阻塞调用线程(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);
}