我需要将 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);
}
这是输出:
因为线程10被转移到一个新的回调,没有给出前一个回调退出和清除信号量的机会,线程永远被锁定。
我该如何解决这个问题?
答案 0 :(得分:2)
切换到任务:
好的msdn文章Tasks and the APM Pattern
public Task<int> SendAsync(Socket socket, byte[] buffer, int offset, int size, SocketFlags flags)
{
var result = socket.BeginSend(buffer, offset, size, flags, _ => { }, socket);
return Task.Factory.FromAsync(result,(r) => socket.EndSend(r));
}
现在事情变得容易一些:
使用默认BlockingCollection<>作为并发队列。它是线程保存并删除列表中的显式锁
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();
}
在不需要信号量的情况下保持非阻塞发送和订单。