【问题标题】:Async httpListener is the callback initiated on the UI thread?Async httpListener 是在 UI 线程上发起的回调吗?
【发布时间】:2012-08-28 13:05:45
【问题描述】:

我希望将 httpListener 构建到一个小型服务器应用程序中。在阅读时,我在this question on stackoverflow 中遇到了以下代码sn-p

Public Class HTTPServer

Shared Listener As HttpListener = New HttpListener

Public Shared Sub Start()

    ServicePointManager.DefaultConnectionLimit = 500
    ServicePointManager.Expect100Continue = False
    ServicePointManager.MaxServicePoints = 500

    Listener.Prefixes.Add("http://localhost/")
    Listener.Start()

    For i As Integer = 1 To (System.Environment.ProcessorCount * 2)

        Dim NewThread As New System.Threading.Thread(AddressOf ListenerThread)
        NewThread.Priority = ThreadPriority.Normal
        NewThread.IsBackground = True
        NewThread.Start()

    Next

End Sub



Private Shared Sub ListenerThread()

    Dim SyncResult As IAsyncResult

    While True

        SyncResult = Listener.BeginGetContext(New AsyncCallback(AddressOf ListenerCallback), Listener)
        SyncResult.AsyncWaitHandle.WaitOne()

    End While

End Sub



Private Shared Sub ListenerCallback(ByVal StateObject As IAsyncResult)

    Dim Listener As HttpListener = DirectCast(StateObject.AsyncState, HttpListener)

    Dim Context As HttpListenerContext = Listener.EndGetContext(StateObject)
    Dim Request As HttpListenerRequest = Context.Request

    Dim Response As HttpListenerResponse = Context.Response

    Dim ResponseString As String = "OK"

    Dim Buffer As Byte() = System.Text.Encoding.UTF8.GetBytes(ResponseString)
    Response.ContentLength64 = Buffer.Length
    Dim OutputStream As System.IO.Stream = Response.OutputStream
    OutputStream.Write(Buffer, 0, Buffer.Length)

    OutputStream.Close()
    OutputStream.Dispose()

End Sub

End Class

这看起来很简单,看起来很像msdn example。但是在一个虚拟项目中测试它时,我发现了一些让我感到困惑的事情,例如 UI 对象可以直接从回调子访问,我认为这应该会导致跨线程异常。

为了澄清,我稍微修改了这段代码,以便在一个简单的 winforms 项目的主窗体中运行。

我似乎没有完全理解代码,例如 AsyncWaitHandle.WaitOne() 对我来说是全新的。

有人能简单介绍一下这个sn-p吗?任何帮助表示赞赏。

【问题讨论】:

    标签: vb.net asynchronous httplistener


    【解决方案1】:

    这个 sn-p 代码看起来像:

    1. 它创建一个 HttpListener 并在 uri 处开始 linsening;
    2. 它创建了一些线程(System.Environment.ProcessorCount * 2 个)来向监听器发送异步请求,尝试获取上下文;
    3. 然后线程被调用SyncResult.AsyncWaitHandle.WaitOne()阻塞了,关于AsyncWaitHandle.WaitOne请查看Details of AsyncWaitHandle.WaitOne
    4. 如果有客户端请求进来,ListenerCallback() 将被触发,并尝试将响应发送回客户端
    5. 然后进入下一轮循环。

    因为它调用了 SyncResult.AsyncWaitHandle.WaitOne() 来阻塞该线程,所以它会得到与 Synchronously 调用相同的结果。

    在我看来,它只是返回一个简单的 OK 响应,所以我们甚至不需要任何其他线程。我会尽快给你看代码(如果我们操作繁重,我的代码将不能很好地工作,请不要在表单申请中这样做。)

        static void Main(string[] args)
        {
            ServicePointManager.DefaultConnectionLimit = 500;
            ServicePointManager.Expect100Continue = false;
            ServicePointManager.MaxServicePoints = 500;
    
            HttpListener listener = new HttpListener();
            listener.Prefixes.Add("http://localhost:999/");
            listener.Start();
            listener.BeginGetContext(ListenerCallBack, listener);
    
    
            Console.ReadLine();
        }
    
        private static void ListenerCallBack(IAsyncResult result)
        {
            HttpListener httpListener = (HttpListener) result.AsyncState;
            // Call EndGetContext to complete the asynchronous operation.
            HttpListenerContext context = httpListener.EndGetContext(result);
            HttpListenerRequest request = context.Request;
            // Obtain a response object.
            HttpListenerResponse response = context.Response;
            // Construct a response. 
            string responseString = "<HTML><BODY> Hello world!</BODY></HTML>";
            byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
            // Get a response stream and write the response to it.
            response.ContentLength64 = buffer.Length;
            System.IO.Stream output = response.OutputStream;
            output.Write(buffer,0,buffer.Length);
            // You must close the output stream.
            output.Close();
    
    
            // we call BeginGetContext() to send async request again for next coming client
            httpListener.BeginGetContext(ListenerCallBack, httpListener);
        }
    

    在 C#5 和 Net 4.5 中,我们有同步方法,它会容易得多:

    static void Main(string[] args)
        {
            ServicePointManager.DefaultConnectionLimit = 500;
            ServicePointManager.Expect100Continue = false;
            ServicePointManager.MaxServicePoints = 500;
    
            HttpListener listener = new HttpListener();
            listener.Prefixes.Add("http://localhost:999/");
            listener.Start();
    
            while (true)
            {
                var t = listener.GetContextAsync();
                HttpListenerContext context = t.Result;
                HttpListenerRequest request = context.Request;
                // Obtain a response object.
                HttpListenerResponse response = context.Response;
                // Construct a response. 
                string responseString = "<HTML><BODY> Hello world!</BODY></HTML>";
                byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
                // Get a response stream and write the response to it.
                response.ContentLength64 = buffer.Length;
                System.IO.Stream output = response.OutputStream;
                output.Write(buffer, 0, buffer.Length);
                // You must close the output stream.
                output.Close();
            }
        }
    

    对于您关于 UI 的“跨线程异常”的另一个问题,您是对的,创建的新 Thread 将为 SynchronizationContext.Current 提供一个空值,因为它是一个线程池线程,我们需要在表单的 SynchronizationContext 中执行一个 post() .这里有更多信息http://msdn.microsoft.com/en-us/magazine/gg598924.aspxhttp://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx

    【讨论】:

      【解决方案2】:

      带有 HttpListener(名称:server)、BackgroundWorker(名称:bw_server)、2 个按钮(名称:btn_start & btn_stop)和 TextBox(名称:tb_log)的简单 C# WebServer:

      public partial class Form1 : Form
      {
          HttpListener server;
          private void btn_start_Click(object sender, EventArgs e)
          {
              bw_server.RunWorkerAsync(@"http://+:80/");
          }
      
          private void bw_server_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
          {
              string prefix = e.Argument.ToString();
              bw_server.ReportProgress(0, "Starting server...");
              if (!bw_server.CancellationPending)
              {
                  try
                  {
                      start_server(prefix);
                  }
                  catch (Exception ex)
                  {
                      bw_server.ReportProgress(0, ex.Message);
                      bw_server.CancelAsync();
                  }
              }
              else
              {
                  e.Cancel = true;
                  return;
              }
          }
      
          private void bw_server_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
          {
              log(e.UserState.ToString());
          }
      
          private void bw_server_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
          {
              if (e.Cancelled)
              {
                  log("Server was stopped");
              }
              else
              {
                  log("Server work was completed");
              }
          }
      
          private void log(string msg)
          {
              if (String.IsNullOrEmpty(msg)) return;
              tb_log.AppendText((tb_log.Text == String.Empty ? String.Empty : Environment.NewLine) + DateTime.Now.ToLongTimeString() + " " + msg);
          }
      
          private void btn_stop_Click(object sender, EventArgs e)
          {
              WebRequest.Create("http://localhost:" + port + "/?stop=1").GetResponse();
              bw_server.CancelAsync();
          }
      
          private void start_server(string prefix)
          {
              server = new HttpListener();
              if (!HttpListener.IsSupported) return;
              if (String.IsNullOrEmpty(prefix))
              {
                  throw new ArgumentException("prefix");
              }
              server.Prefixes.Add(prefix);
              server.Start();
              while (server.IsListening)
              {
                  HttpListenerContext context = server.GetContext();
                  HttpListenerRequest request = context.Request;
                  if (request.HttpMethod == "GET")
                  {
                      HttpListenerResponse response = context.Response;
                      response.ContentType = "text/html; charset=UTF-8";
                      if (request.QueryString["stop"] == "1")
                      {
                          write_to_resp(response, "server was stopped");
                          server.Stop();
                      }
                      else
                      {
                          write_to_resp(response, "bad params");
                      }
                  }
              }
          }
      
          private static void write_to_resp(HttpListenerResponse response, string str_resp)
          {
              byte[] buffer = Encoding.UTF8.GetBytes(str_resp);
              response.ContentLength64 = buffer.Length;
              response.OutputStream.Write(buffer, 0, buffer.Length);
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-05-30
        相关资源
        最近更新 更多