IIS异步控制器操作中的互斥锁实现?

Posted

技术标签:

【中文标题】IIS异步控制器操作中的互斥锁实现?【英文标题】:Mutex implementation within IIS async controller action? 【发布时间】:2020-02-18 21:03:54 【问题描述】:

我编写了一个 Web 服务,它是托管在 IIS 中的 Asp.Net MVC 应用程序。 Web 服务中的数据不是从数据库中检索的,而是从通过 TCP 访问的另一台服务器检索的。我们将其称为数据服务器。 Web 服务可以连接到多个数据服务器。

为了便于讨论,用户通过指定用户名和密码进行身份验证,然后使用适当的数据服务器创建带有套接字的“会话”。假设一切正常 - 我们保持 Socket 处于活动状态并向用户传递一个标识他们所属 Session 的令牌。

对于每个 Socket,我需要防止流量相互中断。我认为最好的方法是以序列化的方式在 Socket 上运行网络流量。我通过使用锁实现了这一点。下面是我在此数据服务器上执行查询的代码。我遇到的问题是,有时我会收到两个冲突的查询,一个可能会挂起。我看到一个查询进来,但看起来它卡在了锁上。 IIS 异步调用的锁定机制是否安全?是否有我可以放入的仪器来确保这实际上是错误?我一直在寻找一段时间,但找不到针对这种特定情况的指导。

 private async Task<string> query(string request, AbstractPermission context = null, bool bUpdateDateTime = true)
        
            try
            
                string reply = "";
                isErrorMsg = false;

                //this lock prevents the keep alive thread from coming in here while we're on the socket
                lock (socketLock)
                
                    sendDone.Reset();
                    receiveDone.Reset();

                    // Send test data to the remote device.  
                    Send(Socket, request);
                    sendDone.WaitOne();

                    // Receive the response from the remote device.
                    Receive(Socket);
                    receiveDone.WaitOne();

                    reply = QueryResponse;

                  //done reading - let's unlock

                if (bUpdateDateTime)
                
                    this.LastUsed = DateTime.Now;
                

                return QueryResponse;
            
            catch (Exception e)
            
                throw e;
            
        


  private void Send(Socket client, String data)
        
            byte[] byteData = Encoding.ASCII.GetBytes(data);

            client.BeginSend(byteData, 0, byteData.Length, 0,
                new AsyncCallback(SendCallback), client);
        

        private void SendCallback(IAsyncResult ar)
        
            try
            
                // Retrieve the socket from the state object.  
                Socket client = (Socket)ar.AsyncState;

                // Complete sending the data to the remote device.  
                int bytesSent = client.EndSend(ar);

                // Signal that all bytes have been sent.  
                sendDone.Set();
            
            catch (Exception e)
            
                Console.WriteLine(e.ToString());
            
        


        private void Receive(Socket client)
        
            try
            
                // Create the state object.  
                StateObject state = new StateObject();
                state.workSocket = client;
                state.PostInitialRead = false;

                // Begin receiving the data from the remote device.  
                client.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None,
                    new AsyncCallback(ReceiveCallback), state);
            
            catch (Exception e)
            
                LogError(e);
            
        

        private void ReceiveCallback(IAsyncResult ar)
        
            try
            
                // Retrieve the state object and the client socket   
                // from the asynchronous state object.  
                StateObject state = (StateObject)ar.AsyncState;
                Socket client = state.workSocket;
                bool PostInitialRead = state.PostInitialRead;

                // Read data from the remote device.  
                int bytesRead = client.EndReceive(ar);

                //
                //
                var thisBatch = Encoding.ASCII.GetString(state.buffer, 0, bytesRead);
                var endIdx = thisBatch.IndexOf('\x04');

                if (!PostInitialRead)
                
                    if (bytesRead == 0)
                        throw new ApplicationException("Timeout waiting for response");

                    if (endIdx != -1)
                    
                        thisBatch = thisBatch.Substring(0, endIdx);
                    
                    if (state.buffer[0] != 0)
                    
                        thisBatch = thisBatch.Substring(1, state.buffer[0]);
                        isErrorMsg = true;
                    
                    else if (state.buffer[1] != 0)
                    
                        thisBatch = thisBatch.Substring(2);
                        isErrorMsg = true;
                    
                    else
                    
                        thisBatch = thisBatch.Substring(2);
                    
                    state.sb.Append(thisBatch);
                    state.PostInitialRead = true;
                
                else
                
                    state.ms.Write(state.buffer, 0, endIdx!=-1?endIdx:bytesRead);
                

                if (endIdx != -1)
                
                    // Got everything
                    state.sb.Append(Encoding.ASCII.GetString(state.ms.ToArray()));
                    QueryResponse = state.sb.ToString();
                    receiveDone.Set();
                    return;
                
                else
                
                    // Get the rest of the data.  
                    client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                        new AsyncCallback(ReceiveCallback), state);
                

            
            catch (Exception e)
            
                Console.WriteLine(e.ToString());
            
        



【问题讨论】:

我认为这是一个不寻常的设计,没有什么好处,是基于 Web 的传统微服务。对于您的锁定问题,wcf 服务或消息总线将更具可维护性和可扩展性。很难说,很可能是您的同步管道,这是第一点。 在此设计中,如果套接字通信出现错误或失败,您的等待会话将永远不会被释放。考虑实现客户端等待的“连接池”模式,然后“签出”连接。如果等待会话无法在短时间内重用会话,请允许它启动一个新会话。 不一定。这些是分发高速事件类型数据的自动收报机的标准设计。设计很奇怪,但是通过会话提供数据服务可以将实时信息分发给按不同标准过滤的不同参与者。 【参考方案1】:

1:在 IIS 中托管这样的东西完全荒谬吗?

没有。看,dotnetcore 和现代开发使用基于 WebSockets 的 SignalR 来处理类似的东西 - 它托管在 IIS 中,所以它并不完全荒谬。

2:我们无法真正回答这个问题。 IO 部分是微不足道的——IIS 可以处理这个。但没有关于数据服务器的详细信息 - 不知道。您通常希望尽可能避免锁定,但这并不意味着完全。 MRSV (multiple Reader, Single Writer),写时复制等可以帮助减少写入,但最后你需要一些锁定。调试这个并不有趣,但这就是人们这样做的原因。

一般应用程序通过将锁定卸载到后端的数据库来避免所有这些 - 许多人花费大量时间优化数据锁定。如果您不能这样做(请记住,我们根本不知道您的数据服务器在内部做什么) - 欢迎来到硬编程。

调试的最佳机会是尝试找到一个重现案例,然后 - 当它被卡住时 - 附加一个调试器。众所周知,这样的东西很难调试 - 但同样,这就像常规编程的***别(省略某些硬件和非常特殊的任务)。

【讨论】:

以上是关于IIS异步控制器操作中的互斥锁实现?的主要内容,如果未能解决你的问题,请参考以下文章

C语言之简单使用互斥锁实现并发控制操作

互斥锁,自旋锁,原子操作原理和实现

线程互斥锁

Java中的锁

C语言之简单使用互斥锁条件锁实现生产者消费者模型操作

TencentOS tiny深度源码分析—— 互斥锁