免责声明:我的C#甚至不如我的C ++那么好。
我正在尝试学习如何在C#中执行异步套接字,以便为我的组件编写测试应用程序。我读了很多方法,但最现代的似乎是使用TcpClient,从中获取NetworkStream,并在返回Task的NetworkStream上调用异步方法。
所以,我开始冒险。
目前尚不清楚如何正确取消异步方法或是否需要。我想在断开连接时手动取消它们吗?有人会认为TcpClient.Close会以一些优雅的方式为我处理。
如果我确实使用手动取消,那么似乎需要一些机制来等待所有异步方法在主线程继续之前退出。在Disconnect()方法的列表中注明。结果是主线程在MakeRequest()或Read()退出之前继续处理和退出,因此有关使用已处置对象的例外情况。
在没有手动取消的测试中,我从异步读取中获得了与使用已经处置的对象相同的异常,因为在退出这些方法之前主线程仍在继续处置和退出。
我想我可以放入自己的同步机制......但是这样做的正确方法是什么?我应该取消吗?是否有一些内置的方法来等待这些方法退出?他们希望我们做什么?
这是我最新的代码尝试:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using log4net;
using System.IO;
using System.Threading;
namespace IntegrationTests
{
public class Client
{
private static readonly ILog log = LogManager.GetLogger("root");
static private ulong m_lastId = 1;
private ulong m_id;
private string m_host;
private uint m_port;
private uint m_timeoutMilliseconds;
private string m_clientId;
private TcpClient m_tcpClient;
private CancellationTokenSource m_cancelationSource;
public Client(string host, uint port, string clientId, uint timeoutMilliseconds)
{
m_id = m_lastId++;
m_host = host;
m_port = port;
m_clientId = clientId;
m_timeoutMilliseconds = timeoutMilliseconds;
m_tcpClient = null;
m_cancelationSource = null;
}
~Client()
{
Disconnect();
}
/// <summary>
/// Attempts to connect to the hostname and port specified in the constructor
/// </summary>
/// <throws cref="System.ApplicationException" on failure
public void Connect()
{
Disconnect();
m_tcpClient = new TcpClient();
m_cancelationSource = new CancellationTokenSource();
try
{
m_tcpClient.Connect(m_host, (int)m_port);
}
catch (SocketException e)
{
string msg = string.Format("Client #{0} failed to connect to {1} on port {2}"
, m_id, m_host, m_port);
throw new System.ApplicationException(msg, e);
}
if (m_tcpClient.Connected)
{
log.Debug(string.Format("Client #{0} connnected to the Component on {1}"
, m_id, m_tcpClient.Client.RemoteEndPoint.ToString()));
}
}
public void Disconnect()
{
if (m_cancelationSource != null)
{
m_cancelationSource.Cancel();
// TODO - There needs to be some kind of wait here until the async methods all return!
// How to do that?
// Are we even supposed to be manually canceling? One would think TcpClient.Close takes care of that,
// however when deleting all cancelation stuff, instead we get exceptions from the async methods about
// using TcpClient's members after it was disposed.
m_cancelationSource.Dispose();
m_cancelationSource = null;
}
if (m_tcpClient != null)
{
m_tcpClient.Close();
m_tcpClient = null;
}
}
public void Login()
{
string loginRequest = string.Format("loginstuff{0}", m_clientId);
var data = Encoding.ASCII.GetBytes(loginRequest);
NetworkStream stream = m_tcpClient.GetStream();
Task writeTask = stream.WriteAsync(data, 0, data.Count());
// This will block until the login is sent
// We want block until the login is sent, so we can be sure we logged in before making requests
if( !writeTask.Wait((int)m_timeoutMilliseconds) )
{
// Error - Send timed out
log.Error(string.Format("Client #{0} Timed out while sending login request to the Component"
, m_id));
}
else
{
log.Debug(string.Format("Client #{0} sent login request to the Component"
, m_id));
}
}
public async void Read()
{
byte[] buffer = new byte[1024];
MemoryStream memoryStream = new MemoryStream();
NetworkStream networkStream = m_tcpClient.GetStream();
Task<int> readTask = null;
bool disconnected = false;
try
{
while (!disconnected)
{
readTask = networkStream.ReadAsync(buffer, 0, buffer.Length, m_cancelationSource.Token);
int bytesReceived = await readTask;
if (readTask.Status == TaskStatus.RanToCompletion)
{
if( bytesReceived <= 0)
{
disconnected = true;
continue;
}
memoryStream.Write(buffer, 0, bytesReceived);
// TODO - Handle parsing of messages in the memory stream
memoryStream.Seek(0, SeekOrigin.Begin);
}
else if (readTask.Status == TaskStatus.Canceled)
{
// Error - Read was cancelled
log.Error(string.Format("Client #{0} Read operation was canceled."
, m_id));
disconnected = true;
continue;
}
else
{
// Error - Unexpected status
log.Error(string.Format("Client #{0} Read operation has unexpected status after returning from await."
, m_id));
}
}
}
catch (System.Exception e)
{
log.Error(string.Format("Client #{0} Exception caught while reading from socket. Exception: {1}"
, m_id, e.ToString()));
}
}
public async void MakeRequest(string thingy)
{
string message = string.Format("requeststuff{0}", thingy);
var data = Encoding.ASCII.GetBytes(message);
NetworkStream networkStream = m_tcpClient.GetStream();
Task writeTask = null;
try
{
writeTask = networkStream.WriteAsync(data, 0, data.Count(), m_cancelationSource.Token);
await writeTask;
if (writeTask.Status == TaskStatus.RanToCompletion)
{
log.Debug(string.Format("Client #{0} sent request for thingy {1} to the Component"
, m_id, thingy));
}
else if (writeTask.Status == TaskStatus.Canceled)
{
// Error - Write was cancelled
log.Error(string.Format("Client #{0} Write operation was canceled while requesting thingy {1} from the Component"
, m_id, thingy));
}
else
{
// Error - Unexpected status
log.Error(string.Format("Client #{0} Write operation has unexpected status after returning from await, while requesting thingy {1} from the Component"
, m_id, thingy));
}
}
catch (System.Exception e)
{
log.Error(string.Format("Client #{0} Exception caught while requesting thingy {1}. Exception: {2}"
, m_id, thingy, e.ToString()));
}
}
}
}
主:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using log4net;
using log4net.Config;
namespace IntegrationTests
{
class Program
{
private static readonly ILog log = LogManager.GetLogger("root");
static void Main(string[] args)
{
try
{
XmlConfigurator.Configure();
log.Info("Starting Component Integration Tests...");
Client client = new Client("127.0.0.1", 24001, "MyClientId", 60000);
client.Connect();
client.Read();
client.Login();
client.MakeRequest("Stuff");
System.Threading.Thread.Sleep(60000);
client.Disconnect();
}
catch (Exception e)
{
log.Error(string.Format("Caught an exception in main. Exception: {0}"
, e.ToString()));
}
}
}
}