Async httpListener是在UI线程上启动的回调吗?

时间:2012-09-03 20:49:35

标签: vb.net asynchronous httplistener

我希望将httpListener构建到一个小型服务器应用程序中。在阅读它的同时,我在this question on stackoverflow

中遇到了以下代码段
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()对我来说是全新的。

有人可以简要介绍一下这个片段吗?任何帮助表示赞赏。

2 个答案:

答案 0 :(得分:2)

此代码段如下:

  1. 它创建一个HttpListener并开始在uri;
  2. 它创建一些线程(System.Environment.ProcessorCount * 2的数量)以向侦听器发送异步请求以尝试获取上下文;
  3. 然后通过调用SyncResult.AsyncWaitHandle.WaitOne()来阻止线程,关于AsyncWaitHandle.WaitOne请查看Details of AsyncWaitHandle.WaitOne
  4. 如果有某个客户端请求进入,则会触发ListenerCallback(),并尝试将响应发送回客户端
  5. 然后进入下一轮循环。
  6. 因为它调用了SyncResult.AsyncWaitHandle.WaitOne()来阻止该线程,它将通过同步调用得到相同的结果。

    在我看来,它只返回一个简单的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

答案 1 :(得分:0)

带有HttpListener的简单C#WebServer(名称:服务器),BackgroundWorker(名称:bw_server),2个按钮(名称:btn_start&amp; btn_stop)和TextBox(名称:tb_log):

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);
    }
}