C# 检测网络流上正在进行的写入操作
假设服务器应用程序通过TCP连接到某个客户端设备。服务器用户决定使用NetworkStream.BeginWrite向客户端设备发送一些消息,但由于连接速度慢或其他未知因素,服务器调用BeginWrite,但BeginWrite尚未完成写入并执行回调。同时,用户决定断开主线程中的客户机设备,导致ObjectDisposedException立即在回调中抛出,因为基础连接不再可用 这里有一个伪例子来说明我的意思:C# 检测网络流上正在进行的写入操作,c#,multithreading,networking,tcp,server,C#,Multithreading,Networking,Tcp,Server,假设服务器应用程序通过TCP连接到某个客户端设备。服务器用户决定使用NetworkStream.BeginWrite向客户端设备发送一些消息,但由于连接速度慢或其他未知因素,服务器调用BeginWrite,但BeginWrite尚未完成写入并执行回调。同时,用户决定断开主线程中的客户机设备,导致ObjectDisposedException立即在回调中抛出,因为基础连接不再可用 这里有一个伪例子来说明我的意思: /// bad pseudo of possible code happening
/// bad pseudo of possible code happening in main server thread
while(!quit){
if( command has been inputted) {
switch (command) {
case send:
string m = getAStringFromGUI();
Send(m);
break;
case disconnect:
ClientDevice.Close();
break;
}
}
}
private void Send(string msg){
NetworkStream stream;
byte[ ] packetBuffer = Encoding.ASCII.GetBytes(msg);
stream = clientDevice.GetStream();
stream.BeginWrite(packetBuffer, 0, packetBuffer.Length, new AsyncCallback(StreamWriteCompleteCallback), stream);
}
private void StreamWriteCompleteCallback(IAsyncResult ar) {
try {
NetworkStream stream = (NetworkStream)ar.AsyncState;
stream.EndWrite(ar);
}
catch (ObjectDisposedException) {
// client device was disconnected by the server before write completed
}
}
如果在send命令之后以足够快的速度输入disconnect命令,将引发异常。显然,在这个简单的示例中,您可以阻塞直到写入完成,但是如果您希望异步进行写入,但在任何写入命令完成(或超时)之前仍然阻止执行断开连接命令,该怎么办?我相信您可以使用waithandles跟踪所有BeginWrite调用,并利用这些来确保在断开连接之前完成写操作,但这似乎需要做很多工作。有没有办法知道是否有线程试图写入给定的网络流 您需要手动跟踪请求,但这比您想象的要简单,一个简单的并发字典就足够了:
//This class will store ongoing requests and also will be used as the async parameter
public class ExecutingRequest
{
public Guid Id { get; set; }
public NetworkStream Stream { get; set; }
}
//Somewhere in your server class
ConcurrentDictionary<Guid, ExecutingRequest> pendingRequests = new ConcurrentDictionary<Guid, ExecutingRequest>();
private void Send(string msg)
{
NetworkStream stream;
byte[ ] packetBuffer = Encoding.ASCII.GetBytes(msg);
stream = clientDevice.GetStream();
var request = new ExecutingRequest{ Stream = stream, Id = Guid.NewGuid() };
pendingRequests.AddOrUpdate(request.Id, request, (a,b) => request);
stream.BeginWrite(packetBuffer, 0, packetBuffer.Length, new AsyncCallback(StreamWriteCompleteCallback), request);
}
private void StreamWriteCompleteCallback(IAsyncResult ar)
{
try {
ExecutingRequest req = (ExecutingRequest)ar.AsyncState;
pendingRequests.TryRemove(req.Id, out ExecutingRequest dummy);
req.stream.EndWrite(ar);
}
catch (ObjectDisposedException)
{
// client device was disconnected by the server before write completed
}
}
//此类将存储正在进行的请求,还将用作异步参数
公共类执行请求
{
公共Guid Id{get;set;}
公共网络流{get;set;}
}
//服务器类中的某个地方
ConcurrentDictionary pendingRequests=新建ConcurrentDictionary();
私有void发送(字符串消息)
{
网络流;
byte[]packetBuffer=Encoding.ASCII.GetBytes(msg);
stream=clientDevice.GetStream();
var request=newexecutingrequest{Stream=Stream,Id=Guid.NewGuid()};
pendingRequests.AddOrUpdate(request.Id,request,(a,b)=>request);
stream.BeginWrite(packetBuffer,0,packetBuffer.Length,新异步回调(StreamWriteCompleteCallback),request);
}
私有void StreamWriteCompleteCallback(IAsyncResult ar)
{
试一试{
ExecutingRequest req=(ExecutingRequest)ar.AsyncState;
pendingRequests.TryRemove(req.Id,out-ExecutingRequest-dummy);
请求流结束写入(ar);
}
捕获(ObjectDisposedException)
{
//写入完成之前,服务器已断开客户端设备的连接
}
}
然后,您可以通过检查字典的长度来检查是否有任何请求正在运行。您需要手动跟踪请求,但这比您想象的要简单,一个简单的并发字典就足够了:
//This class will store ongoing requests and also will be used as the async parameter
public class ExecutingRequest
{
public Guid Id { get; set; }
public NetworkStream Stream { get; set; }
}
//Somewhere in your server class
ConcurrentDictionary<Guid, ExecutingRequest> pendingRequests = new ConcurrentDictionary<Guid, ExecutingRequest>();
private void Send(string msg)
{
NetworkStream stream;
byte[ ] packetBuffer = Encoding.ASCII.GetBytes(msg);
stream = clientDevice.GetStream();
var request = new ExecutingRequest{ Stream = stream, Id = Guid.NewGuid() };
pendingRequests.AddOrUpdate(request.Id, request, (a,b) => request);
stream.BeginWrite(packetBuffer, 0, packetBuffer.Length, new AsyncCallback(StreamWriteCompleteCallback), request);
}
private void StreamWriteCompleteCallback(IAsyncResult ar)
{
try {
ExecutingRequest req = (ExecutingRequest)ar.AsyncState;
pendingRequests.TryRemove(req.Id, out ExecutingRequest dummy);
req.stream.EndWrite(ar);
}
catch (ObjectDisposedException)
{
// client device was disconnected by the server before write completed
}
}
//此类将存储正在进行的请求,还将用作异步参数
公共类执行请求
{
公共Guid Id{get;set;}
公共网络流{get;set;}
}
//服务器类中的某个地方
ConcurrentDictionary pendingRequests=新建ConcurrentDictionary();
私有void发送(字符串消息)
{
网络流;
byte[]packetBuffer=Encoding.ASCII.GetBytes(msg);
stream=clientDevice.GetStream();
var request=newexecutingrequest{Stream=Stream,Id=Guid.NewGuid()};
pendingRequests.AddOrUpdate(request.Id,request,(a,b)=>request);
stream.BeginWrite(packetBuffer,0,packetBuffer.Length,新异步回调(StreamWriteCompleteCallback),request);
}
私有void StreamWriteCompleteCallback(IAsyncResult ar)
{
试一试{
ExecutingRequest req=(ExecutingRequest)ar.AsyncState;
pendingRequests.TryRemove(req.Id,out-ExecutingRequest-dummy);
请求流结束写入(ar);
}
捕获(ObjectDisposedException)
{
//写入完成之前,服务器已断开客户端设备的连接
}
}
然后,您可以通过检查字典的长度来检查是否有任何请求正在运行。这是不够的,因为它不能解决一个线程的争用条件,一个线程检查了字典,即将调用
.Dispose()
,而另一个线程将调用.BeginWrite()
。如果要排除任何.BeginWrite()
被.Dispose()
中断的可能性,则必须存在互斥。另外,从技术上讲,不需要跟踪单个请求,您只需跟踪未完成请求的数量即可。@Jeroenmoster只是想澄清一下-如果我想关闭特定连接,但首先等待其请求完成,我是否需要跟踪所有单个请求?否则,您怎么知道当前未完成的请求与您试图连接的连接有关close@Cobalt:我的意思是“每个流的未完成请求数”。我个人会将其抽象为一个类,该类封装了客户端和在该连接上发出的任何请求,但即使您在这里使用了一个中央集合(我不会这样做,因为您正在设置一个锁定热点),它也足以增加/减少每个流的挂起计数。跟踪整个请求可能对诊断有一定价值。@Jeroenmoster啊,我明白你的意思了。至于争用条件,是否可以简单地用互斥体包装每个连接,并用类似于mutex.WaitOne()
和mutex.ReleaseMutex()的东西将每次尝试写入或关闭连接的行为封装起来?还是我在想这个wrong@Cobalt:一个监视器
比一个完整的互斥锁更简单。关闭:lock(client){while(pendingRequestCount!=0)Monitor.Wait(client);client.Close();}
Send:lock(client){++pendingRequestCount;};发送(…)代码>。完成回调:lock(client){--pendingRequestCount;if(pendingRequestCount==0)Monitor.pulsell(client);}
。但是,要小心被中止的请求,计数必须正确,否则您将失败