C# 对异步方法BeginSend的队列调用套接字

C# 对异步方法BeginSend的队列调用套接字,c#,multithreading,sockets,asynchronous,C#,Multithreading,Sockets,Asynchronous,我需要将BeginSend调用排队到套接字,并且需要按时间顺序执行它们。为了做到这一点,我使用了一个信号量来在回调函数清除时发出信号。 大多数情况下,它是有效的,因为每个异步回调都是在一个单独的线程上执行的,但偶尔在新的异步调用中使用当前回调中使用的相同线程。当这种情况发生时,该线程被锁定,等待信号量被释放,但由于本应清除信号量的同一线程正在等待信号量被清除,因此该线程被永远锁定 下面是一个测试代码来说明问题: static Semaphore semaphore = new Semaphore

我需要将BeginSend调用排队到套接字,并且需要按时间顺序执行它们。为了做到这一点,我使用了一个信号量来在回调函数清除时发出信号。
大多数情况下,它是有效的,因为每个异步回调都是在一个单独的线程上执行的,但偶尔在新的异步调用中使用当前回调中使用的相同线程。当这种情况发生时,该线程被锁定,等待信号量被释放,但由于本应清除信号量的同一线程正在等待信号量被清除,因此该线程被永远锁定

下面是一个测试代码来说明问题:

static Semaphore semaphore = new Semaphore(1, 1);
static IList<byte[]> buffer = new List<byte[]>();
static void Main()
{
    Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    socket.Connect(new IPEndPoint(new IPAddress(new byte[] { 192, 168, 1, 8 }), 123));
    while (true) // data feed
    {
        lock (buffer)
        {
            buffer.Add(new byte[1460]);
            if (buffer.Count == 1)
                socket.BeginSend(buffer[0], 0, 1460, 0, new AsyncCallback(SendCallback), socket); // calls BeginSend if the buffer was empty before 
        }
    }
}

static void SendCallback(IAsyncResult ar)
{
    Console.WriteLine("in " + Thread.CurrentThread.ManagedThreadId);
    semaphore.WaitOne();
    Socket socket = (Socket)ar.AsyncState;
    lock(buffer)
    {
        buffer.RemoveAt(0); // removes data that was sent
        if (buffer.Count > 0) // if there is more data to send calls BeginSend again
            socket.BeginSend(buffer[0], 0, 1460, 0, new AsyncCallback(SendCallback), socket);
    }
    semaphore.Release();
    Console.WriteLine("out " + Thread.CurrentThread.ManagedThreadId);
}
静态信号量信号量=新信号量(1,1);
静态IList缓冲区=新列表();
静态void Main()
{
Socket Socket=新套接字(AddressFamily.InterNetwork、SocketType.Stream、ProtocolType.Tcp);
Connect(新的IPEndPoint(新的IPAddress(新的字节[]{192,168,1,8}),123));
while(true)//数据馈送
{
锁(缓冲区)
{
Add(新字节[1460]);
如果(buffer.Count==1)
BeginSend(缓冲区[0],0,1460,0,新异步回调(SendCallback),socket);//如果之前缓冲区为空,则调用BeginSend
}
}
}
静态void SendCallback(IAsyncResult ar)
{
Console.WriteLine(“in”+Thread.CurrentThread.ManagedThreadId);
WaitOne()信号量;
套接字套接字=(套接字)ar.AsyncState;
锁(缓冲区)
{
buffer.RemoveAt(0);//删除已发送的数据
if(buffer.Count>0)//如果有更多数据要发送,请重新开始发送
BeginSend(缓冲区[0],0,1460,0,新的异步回调(SendCallback),socket);
}
semaphore.Release();
WriteLine(“out”+Thread.CurrentThread.ManagedThreadId);
}
下面是输出:

因为线程10被转移到一个新的回调,而没有给上一个回调退出并清除信号量的机会,所以线程被永远锁定

如何解决此问题?

切换到任务:

漂亮的msdn文章

公共任务SendAsync(套接字、字节[]缓冲区、int偏移量、int大小、SocketFlags) { var result=socket.BeginSend(缓冲区、偏移量、大小、标志,=>{},socket); 返回Task.Factory.fromsync(result,(r)=>socket.EndSend(r)); } 现在事情变得容易了一些:

使用默认值作为并发队列。它是线程保存并删除列表上的显式锁

static BlockingCollection<byte[]> buffer = new BlockingCollection<byte[]>();

public async void Main()
{
   Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
   socket.Connect(new IPEndPoint(new IPAddress(new byte[] { 192, 168, 1, 8 }), 123));
   while (!buffer.IsCompleted)
   {
      var data = buffer.Take();
      await SendAsync(socket, data, 0, data.Length, 0);
   }
   Console.ReadLine();            
}
static BlockingCollection buffer=new BlockingCollection();
公共异步void Main()
{
Socket Socket=新套接字(AddressFamily.InterNetwork、SocketType.Stream、ProtocolType.Tcp);
Connect(新的IPEndPoint(新的IPAddress(新的字节[]{192,168,1,8}),123));
而(!buffer.IsCompleted)
{
var data=buffer.Take();
等待SendAsync(套接字,数据,0,数据.Length,0);
}
Console.ReadLine();
}

在不需要信号量的情况下维护非阻塞发送和顺序。

我不知道Socket.beginhead可以同步调用回调。这就产生了可怕的重入问题。你确定吗?你能发布一个callstack的截图来证明这一点吗?SendCallback中应有两个堆栈帧。使用BlockingCollection跟踪要发送的数据。解决2个问题:维护订单和删除deadlock@usr的确那是个可怕的陷阱。可能是以效率的名义添加的。这也使得BeginSend不是非阻塞的,因为任意代码可以作为调用的一部分运行。您现在有一个竞争条件,因为您正在访问锁外的缓冲区。我不知道什么任务会改变。任务完成仍然可以是同步的,并且会导致重入问题。您已将问题结构更改为一个while循环,以避免出现问题,但OP需要锁并防止并发写入可能是有原因的。@请让OP更新问题并解释原因,也许我们可以调整答案以更好地满足他的需要。在我的真实问题中,一个线程在到达缓冲区列表时逐包添加数据包,如果之前缓冲区中没有数据包,则调用BeginSend。然后,如果有更多数据包要发送,BeginSend回调函数将再次调用BeginSend。不确定任务会有什么帮助。它基本上是用一个同步方法来转换BeginSend。@Chris,它看起来像一个同步方法,但实际上不是。
static BlockingCollection<byte[]> buffer = new BlockingCollection<byte[]>();

public async void Main()
{
   Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
   socket.Connect(new IPEndPoint(new IPAddress(new byte[] { 192, 168, 1, 8 }), 123));
   while (!buffer.IsCompleted)
   {
      var data = buffer.Take();
      await SendAsync(socket, data, 0, data.Length, 0);
   }
   Console.ReadLine();            
}